Move *.c and *.h into server/ directory.
authorArt Cancro <ajc@citadel.org>
Thu, 8 Sep 2022 23:17:41 +0000 (19:17 -0400)
committerArt Cancro <ajc@citadel.org>
Thu, 8 Sep 2022 23:17:41 +0000 (19:17 -0400)
44 files changed:
webcit-ng/.gitignore
webcit-ng/Makefile
webcit-ng/admin_functions.c [deleted file]
webcit-ng/caldav_reports.c [deleted file]
webcit-ng/ctdl_commands.c [deleted file]
webcit-ng/ctdlclient.c [deleted file]
webcit-ng/ctdlfunctions.c [deleted file]
webcit-ng/floor_functions.c [deleted file]
webcit-ng/forum_view.c [deleted file]
webcit-ng/html2html.c [deleted file]
webcit-ng/http.c [deleted file]
webcit-ng/main.c [deleted file]
webcit-ng/messages.c [deleted file]
webcit-ng/request.c [deleted file]
webcit-ng/room_functions.c [deleted file]
webcit-ng/server/admin_functions.c [new file with mode: 0644]
webcit-ng/server/caldav_reports.c [new file with mode: 0644]
webcit-ng/server/ctdl_commands.c [new file with mode: 0644]
webcit-ng/server/ctdlclient.c [new file with mode: 0644]
webcit-ng/server/ctdlfunctions.c [new file with mode: 0644]
webcit-ng/server/floor_functions.c [new file with mode: 0644]
webcit-ng/server/forum_view.c [new file with mode: 0644]
webcit-ng/server/html2html.c [new file with mode: 0644]
webcit-ng/server/http.c [new file with mode: 0644]
webcit-ng/server/main.c [new file with mode: 0644]
webcit-ng/server/messages.c [new file with mode: 0644]
webcit-ng/server/request.c [new file with mode: 0644]
webcit-ng/server/room_functions.c [new file with mode: 0644]
webcit-ng/server/static.c [new file with mode: 0644]
webcit-ng/server/tcp_sockets.c [new file with mode: 0644]
webcit-ng/server/text2html.c [new file with mode: 0644]
webcit-ng/server/tls.c [new file with mode: 0644]
webcit-ng/server/user_functions.c [new file with mode: 0644]
webcit-ng/server/util.c [new file with mode: 0644]
webcit-ng/server/webcit.h [new file with mode: 0644]
webcit-ng/server/webserver.c [new file with mode: 0644]
webcit-ng/static.c [deleted file]
webcit-ng/tcp_sockets.c [deleted file]
webcit-ng/text2html.c [deleted file]
webcit-ng/tls.c [deleted file]
webcit-ng/user_functions.c [deleted file]
webcit-ng/util.c [deleted file]
webcit-ng/webcit.h [deleted file]
webcit-ng/webserver.c [deleted file]

index 425df94f3589ac382edb0a92ba965a1a3793d2fb..19f1cbfce0b7652934d50f7ca8edd8048adcf38d 100644 (file)
@@ -1,3 +1,4 @@
 *.d
 *.o
+*~
 webcit
index 0fc1ded6d18d7b9cc44c04e242f016768f3ff210..cda937007a98555c7e34b80dd9f5e8b0b1a48c3d 100644 (file)
@@ -1,24 +1,13 @@
-OBJS := http.o main.o request.o tls.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 forum_view.o html2html.o text2html.o user_functions.o \
-       floor_functions.o
 CFLAGS := -ggdb -Wno-format-truncation
 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
+webcit: server/*.c
+       gcc server/*.c $(LDFLAGS) -lcitadel -lpthread -lcrypto -lssl -lexpat -o webcit
 
 # remove compilation products
 clean:
-       rm -f webcit *.o *.d
+       rm -f webcit
 
 distclean: clean
 
diff --git a/webcit-ng/admin_functions.c b/webcit-ng/admin_functions.c
deleted file mode 100644 (file)
index 2c2d85f..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-// Admin functions
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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), BASE64_NO_LINEBREAKS);
-
-       syslog(LOG_DEBUG, "try_login(username='%s',password=(%d bytes))", username, (int) 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
-
-       JsonValue *j = NewJsonObject(HKEY("login"));                            // Compose a JSON object with the results
-       if (buf[0] == '2') {
-               JsonObjectAppend(j, NewJsonBool(HKEY("result"), 1));
-               JsonObjectAppend(j, NewJsonPlainString(HKEY("message"), "logged in", -1));
-               extract_token(username, &buf[4], 0, '|', sizeof username);      // This will have the proper capitalization etc.
-               JsonObjectAppend(j, NewJsonPlainString(HKEY("fullname"), username, -1));
-               JsonObjectAppend(j, NewJsonNumber(HKEY("axlevel"), extract_int(&buf[4], 1) ));
-               JsonObjectAppend(j, NewJsonNumber(HKEY("timescalled"), extract_long(&buf[4], 2) ));
-               JsonObjectAppend(j, NewJsonNumber(HKEY("posted"), extract_long(&buf[4], 3) ));
-               JsonObjectAppend(j, NewJsonNumber(HKEY("usernum"), extract_long(&buf[4], 5) ));
-               JsonObjectAppend(j, NewJsonNumber(HKEY("previous_login"), extract_long(&buf[4], 6) ));
-       }
-       else {
-               JsonObjectAppend(j, NewJsonBool(HKEY("result"), 0));
-               JsonObjectAppend(j, NewJsonPlainString(HKEY("message"), &buf[4], -1));
-       }
-       StrBuf *sj = NewStrBuf();
-       SerializeJson(sj, j, 1);                                                // '1' == free the source object
-
-       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);
-}
-
-
-// /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->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->url, "/ctdl/a/login")) {     // log in
-               try_login(h, c);
-               return;
-       }
-
-       if (!strcasecmp(h->url, "/ctdl/a/logout")) {    // log out
-               logout(h, c);
-               return;
-       }
-
-       if (!strcasecmp(h->url, "/ctdl/a/whoami")) {    // return display name of user
-               whoami(h, c);
-               return;
-       }
-
-       do_404(h);                                      // unknown
-}
diff --git a/webcit-ng/caldav_reports.c b/webcit-ng/caldav_reports.c
deleted file mode 100644 (file)
index 9bcca61..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-//
-// This file contains functions which handle all of the CalDAV "REPORT" queries
-// specified in RFC4791 section 7.
-//
-// Copyright (c) 2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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.
-void 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);
-}
diff --git a/webcit-ng/ctdl_commands.c b/webcit-ng/ctdl_commands.c
deleted file mode 100644 (file)
index 6420a87..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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->url, "/ctdl/c/info")) {
-               serv_info(h, c);
-       }
-       else {
-               do_404(h);
-       }
-}
diff --git a/webcit-ng/ctdlclient.c b/webcit-ng/ctdlclient.c
deleted file mode 100644 (file)
index fa35595..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-// Functions that handle communication with a Citadel Server
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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 specific number of bytes of binary data from the Citadel server.
-// Returns the number of bytes read or -1 for error.
-int ctdl_read_binary(struct ctdlsession *ctdl, char *buf, int bytes_requested) {
-       int bytes_read = 0;
-       int c = 0;
-
-       while (bytes_read < bytes_requested) {
-               c = read(ctdl->sock, &buf[bytes_read], bytes_requested-bytes_read);
-               if (c <= 0) {
-                       syslog(LOG_DEBUG, "Socket error or zero-length read");
-                       return (-1);
-               }
-               bytes_read += c;
-       }
-       return (bytes_read);
-}
-
-
-// Read a newline-terminated line of text from the Citadel server.
-// 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 (-1);
-       }
-
-       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, "\033[32m[ %s\033[0m", buf);
-                       return (len);
-               }
-               ++len;
-       }
-       // syslog(LOG_DEBUG, "\033[32m[ %s\033[0m", 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)), 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, "\033[32m] %s\033[0m", 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;
-}
-
-
-// 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;
-       }
-
-       memset(authbuf, 0, authbuflen);
-
-       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, password=(%d bytes)", 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') {
-               extract_token(c->whoami, &buf[4], 0, '|', sizeof c->whoami);
-               syslog(LOG_DEBUG, "Logged in as %s", c->whoami);
-
-               // Re-encode the auth string so it contains the properly formatted username
-               char new_auth_string[1024];
-               snprintf(new_auth_string, sizeof(new_auth_string),  "%s:%s", c->whoami, supplied_password);
-               CtdlEncodeBase64(c->auth, new_auth_string, strlen(new_auth_string), BASE64_NO_LINEBREAKS);
-
-               return(0);
-       }
-
-       syslog(LOG_DEBUG, "Login failed: %s", &buf[4]);
-       return(1);              // login failed; resultbuf will explain why
-}
-
-
-// This is a variant of the "server connection pool" design pattern.  We go through our list
-// of connections to Citadel Server, looking for a connection that is at once:
-// 1. Not currently serving a WebCit transaction (is_bound)
-// 2a. Is logged in to Citadel as the correct user, if the HTTP session is logged in; or
-// 2b. Is NOT logged in to Citadel, if the HTTP session is not logged in.
-// If we find a qualifying connection, we bind to it for the duration of this WebCit HTTP transaction.
-// Otherwise, we create a new connection to Citadel Server and add it to the pool.
-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);
-
-       // 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);                                           // Could not create a new session (yikes!)
-       }
-
-       if (my_session->sock < 3) {
-               is_new_session = 1;
-       }
-       else {                                                          // make sure our Citadel session is still working
-               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, "");
-               static char *ctdl_sock_path = NULL;
-               if (!ctdl_sock_path) {
-                       ctdl_sock_path = malloc(PATH_MAX);
-                       snprintf(ctdl_sock_path, PATH_MAX, "%s/citadel.socket", ctdl_dir);
-               }
-               my_session->sock = uds_connectsock(ctdl_sock_path);
-               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);
-}
diff --git a/webcit-ng/ctdlfunctions.c b/webcit-ng/ctdlfunctions.c
deleted file mode 100644 (file)
index ba25fd6..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// These utility functions loosely make up a Citadel protocol client library.
-//
-// Copyright (c) 2016-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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);
-}
diff --git a/webcit-ng/floor_functions.c b/webcit-ng/floor_functions.c
deleted file mode 100644 (file)
index ccb6326..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// Floor functions
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#include "webcit.h"
-
-
-// Dispatcher for "/ctdl/f" and "/ctdl/f/" for the floor list
-void floor_list(struct http_transaction *h, struct ctdlsession *c) {
-       char buf[1024];
-       char floorname[1024];
-
-       ctdl_printf(c, "LFLR");
-       ctdl_readline(c, buf, sizeof(buf));
-       if (buf[0] != '1') {
-               do_502(h);
-               return;
-       }
-
-       JsonValue *j = NewJsonArray(HKEY("lflr"));
-       while (ctdl_readline(c, buf, sizeof(buf)), strcmp(buf, "000")) {
-
-               // 0  |1   |2
-               // num|name|refcount
-               JsonValue *jr = NewJsonObject(HKEY("floor"));
-
-               extract_token(floorname, buf, 1, '|', sizeof floorname);
-               JsonObjectAppend(jr, NewJsonPlainString(HKEY("name"), floorname, -1));
-
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("num"), extract_int(buf, 0)));
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("refcount"), extract_int(buf, 2)));
-
-               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/f/
-// (This is a stub ... we will need to add more functions when we can do more than just a floor list)
-void ctdl_f(struct http_transaction *h, struct ctdlsession *c) {
-       floor_list(h, c);
-       return;
-}
diff --git a/webcit-ng/forum_view.c b/webcit-ng/forum_view.c
deleted file mode 100644 (file)
index 90ef337..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-// The code in here feeds messages out as JSON to the client browser.  It is currently being used
-// for the forum view, but as we implement other views we'll probably reuse a lot of what's here.
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#include "webcit.h"
-
-// Commands we need to send to Citadel Server before we begin rendering forum view.
-// These are common to flat and threaded views.
-void setup_for_forum_view(struct ctdlsession *c) {
-       char buf[1024];
-       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
-}
-
-
-// Fetch a single message and return it in JSON format for client-side rendering
-void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum) {
-       StrBuf *raw_msg = NULL;
-       StrBuf *sanitized_msg = NULL;
-       char buf[1024];
-       char content_transfer_encoding[1024] = { 0 };
-       char content_type[1024] = { 0 };
-       char datetime[128] = { 0 };
-       char author[1024] = { 0 };
-       char emailaddr[1024] = { 0 };
-       int message_originated_locally = 0;
-
-       setup_for_forum_view(c);
-
-       ctdl_printf(c, "MSG4 %ld", msgnum);
-       ctdl_readline(c, buf, sizeof(buf));
-       if (buf[0] != '1') {
-               do_404(h);
-               return;
-       }
-
-       JsonValue *j = NewJsonObject(HKEY("message"));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("msgnum"), msgnum));
-
-       while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000"))) {
-               utf8ify_rfc822_string(&buf[5]);
-
-               // citadel header parsing here
-               if (!strncasecmp(buf, "from=", 5)) {
-                       safestrncpy(author, &buf[5], sizeof author);
-               }
-               else if (!strncasecmp(buf, "rfca=", 5)) {
-                       safestrncpy(emailaddr, &buf[5], sizeof emailaddr);
-               }
-               else if (!strncasecmp(buf, "time=", 5)) {
-                       JsonObjectAppend(j, NewJsonNumber(HKEY("time"), atol(&buf[5])));
-               }
-               else if (!strncasecmp(buf, "locl=", 5)) {
-                       message_originated_locally = 1;
-               }
-               else if (!strncasecmp(buf, "subj=", 5)) {
-                       JsonObjectAppend(j, NewJsonPlainString(HKEY("subj"), &buf[5], -1));
-               }
-               else if (!strncasecmp(buf, "msgn=", 5)) {
-                       JsonObjectAppend(j, NewJsonPlainString(HKEY("msgn"), &buf[5], -1));
-               }
-               else if (!strncasecmp(buf, "wefw=", 5)) {
-                       JsonObjectAppend(j, NewJsonPlainString(HKEY("wefw"), &buf[5], -1));
-               }
-       }
-
-       if (message_originated_locally) {
-               JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), author, -1));
-       }
-       else {
-               JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), emailaddr, -1));           // FIXME do compound address string
-       }
-
-       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);
-                       }
-               }
-               if (!strcmp(buf, "000")) {              // if we have an empty message, don't try to read further
-                       raw_msg = NULL;
-               }
-               else {
-                       raw_msg = ctdl_readtextmsg(c);
-               }
-       }
-       else {
-               raw_msg = NULL;
-       }
-
-       if (raw_msg) {
-               // These are the encodings we know how to handle.  Decode in-place.
-
-               if (!strcasecmp(content_transfer_encoding, "base64")) {
-                       StrBufDecodeBase64(raw_msg);
-               }
-               if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
-                       StrBufDecodeQP(raw_msg);
-               }
-
-               // At this point, raw_msg contains the decoded message.
-               // Now run through the renderers we have available.
-
-               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>"));
-                       syslog(LOG_WARNING, "forum_view: no renderer for content type %s", content_type);
-               }
-               FreeStrBuf(&raw_msg);
-
-               // If sanitized_msg is not NULL, we have rendered the message and can output it.
-               if (sanitized_msg) {
-                       JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
-               }
-               else {
-                       syslog(LOG_WARNING, "forum_view: message %ld of content type %s converted to NULL", msgnum, content_type);
-               }
-       }
-
-       StrBuf *sj = NewStrBuf();
-       SerializeJson(sj, j, 1);        // '1' == free the source object
-
-       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);
-}
diff --git a/webcit-ng/html2html.c b/webcit-ng/html2html.c
deleted file mode 100644 (file)
index b6dadc3..0000000
+++ /dev/null
@@ -1,635 +0,0 @@
-//
-// Output an HTML message, modifying it slightly to make sure it plays nice
-// with the rest of our web framework.
-//
-// Copyright (c) 2005-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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
-
-       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.
-               // FIXME change URL syntax for webcit-ng
-               if (!strncasecmp(ptr, "<a href=\"mailto:", 16)) {
-                       content_length += 64;
-                       StrBufAppendPrintf(converted_msg, "<a href=\"display_enter?force_room=_MAIL_?recp=");
-                       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/<msgnum>/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, "&nbsp;");
-                               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';
-}
diff --git a/webcit-ng/http.c b/webcit-ng/http.c
deleted file mode 100644 (file)
index 55c335e..0000000
+++ /dev/null
@@ -1,327 +0,0 @@
-// This module handles HTTP transactions.
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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 (-1);
-       }
-
-       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 keyval new_response_header;
-       new_response_header.key = key;
-       new_response_header.val = val;
-       array_append(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;
-       int i;                                  // general purpose iterator variable
-
-       memset(&h, 0, sizeof h);
-       h.request_headers = array_new(sizeof(struct keyval));
-       h.request_parms = array_new(sizeof(struct keyval));
-       h.response_headers = array_new(sizeof(struct keyval));
-
-       while (len = client_readline(ch, buf, sizeof buf), (len > 0)) {
-               ++lines_read;
-
-               if (lines_read == 1) {          // First line is method and URL.
-                       c = strchr(buf, ' ');   // IGnore the HTTP-version.
-                       if (c == NULL) {
-                               h.method = strdup("NULL");
-                               h.url = strdup("/");
-                       }
-                       else {
-                               *c = 0;
-                               h.method = strdup(buf);
-                               ++c;
-                               d = c;
-                               c = strchr(d, ' ');
-                               if (c != NULL) {
-                                       *c = 0;
-                               }
-                               ++c;
-                               h.url = 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 keyval new_request_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);
-                               array_append(h.request_headers, &new_request_header);
-#ifdef DEBUG_HTTP
-                               syslog(LOG_DEBUG, "\033[1m\033[35m{ %s: %s\033[0m", new_request_header.key, new_request_header.val);
-#endif
-                       }
-               }
-
-       }
-
-       // If the URL had any query parameters in it, parse them out now.
-       char *p = (h.url ? strchr(h.url, '?') : NULL);
-       if (p) {
-               *p++ = 0;                                               // insert a null to remove parameters from the URL
-               char *tok, *saveptr = NULL;
-               for (tok = strtok_r(p, "&", &saveptr); tok!=NULL; tok = strtok_r(NULL, "&", &saveptr)) {
-                       char *eq = strchr(tok, '=');
-                       if (eq) {
-                               *eq++ = 0;
-                               unescape_input(eq);
-                               struct keyval kv;
-                               kv.key = strdup(tok);
-                               kv.val = strdup(eq);
-                               array_append(h.request_parms, &kv);
-#ifdef DEBUG_HTTP
-                               syslog(LOG_DEBUG, "\033[1m\033[33m| %s = %s\033[0m", kv.key, kv.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 {
-//#ifdef DEBUG_HTTP
-               syslog(LOG_DEBUG, "\033[33m\033[1m< %s %s\033[0m", h.method, h.url);
-//#endif
-
-               // 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 the entire request body to stderr -- not what you want during normal operation.
-                       #ifdef BODY_TO_STDERR
-                       write(2, HKEY("\033[31m"));
-                       write(2, h.request_body, h.request_body_length);
-                       write(2, HKEY("\033[0m\n"));
-                       #endif
-
-               }
-
-               // Now pass control up to the next layer to perform the request.
-               perform_request(&h);
-
-               // Write the entire response body to stderr -- not what you want during normal operation.
-               #ifdef BODY_TO_STDERR
-               write(2, HKEY("\033[32m"));
-               write(2, h.response_body, h.response_body_length);
-               write(2, HKEY("\033[0m\n"));
-               #endif
-
-               // Output the results back to the client.
-#ifdef DEBUG_HTTP
-               syslog(LOG_DEBUG, "\033[33m\033[1m> %03d %s\033[0m", h.response_code, h.response_string);
-#endif
-               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
-               int number_of_response_headers = array_len(h.response_headers);
-               for (i=0; i<number_of_response_headers; ++i) {
-                       struct keyval *kv = array_get_element_at(h.response_headers, i);
-#ifdef DEBUG_HTTP
-                       syslog(LOG_DEBUG, "\033[1m\033[35m} %s: %s\033[0m", kv->key, kv->val);
-#endif
-                       client_printf(ch, "%s: %s\r\n", kv->key, kv->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 (array_len(h.request_headers) > 0) {
-               struct keyval *kv = array_get_element_at(h.request_headers, 0);
-               if (kv->key) free(kv->key);
-               if (kv->val) free(kv->val);
-               array_delete_element_at(h.request_headers, 0);
-       }
-       array_free(h.request_headers);
-       while (array_len(h.request_parms) > 0) {
-               struct keyval *kv = array_get_element_at(h.request_parms, 0);
-               if (kv->key) free(kv->key);
-               if (kv->val) free(kv->val);
-               array_delete_element_at(h.request_parms, 0);
-       }
-       array_free(h.request_parms);
-       while (array_len(h.response_headers) > 0) {
-               struct keyval *kv = array_get_element_at(h.response_headers, 0);
-               if (kv->key) free(kv->key);
-               if (kv->val) free(kv->val);
-               array_delete_element_at(h.response_headers, 0);
-       }
-       array_free(h.response_headers);
-       if (h.method) {
-               free(h.method);
-       }
-       if (h.url) {
-               free(h.url);
-       }
-       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 keyval *kv;
-       int i;
-       for (i=0; i<array_len(h->request_headers); ++i) {
-               kv = array_get_element_at(h->request_headers, i);
-               if (!strcasecmp(kv->key, requested_header)) {
-                       return (kv->val);
-               }
-       }
-       return (NULL);
-}
-
-
-// Utility function to fetch a specific URL parameter from a completely read-in request.
-// Returns the value of the requested parameter, or NULL if the specified parameter 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 *get_url_param(struct http_transaction *h, char *requested_param) {
-       struct keyval *kv;
-       int i;
-       for (i=0; i<array_len(h->request_parms); ++i) {
-               kv = array_get_element_at(h->request_parms, i);
-               if (!strcasecmp(kv->key, requested_param)) {
-                       return (kv->val);
-               }
-       }
-       return (NULL);
-}
diff --git a/webcit-ng/main.c b/webcit-ng/main.c
deleted file mode 100644 (file)
index 0313691..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-// Main entry point for the program.
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#include "webcit.h"            // All other headers are included from this header.
-
-char *ctdl_dir = CTDL_DIR;
-
-// 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':
-                       ctdl_dir = strdup(optarg);
-                       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]"
-                               "[-h citadel_server_directory]\n"
-                       );
-                       return 1;
-               }
-
-       while (optind < argc) {
-               ctdl_dir = strdup(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-2022 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(1, 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);
-}
diff --git a/webcit-ng/messages.c b/webcit-ng/messages.c
deleted file mode 100644 (file)
index 1ef039f..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-//
-// Message base functions
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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;
-       }
-
-       char *wefw = get_url_param(h, "wefw");  // references
-       if (!wefw) wefw = "";
-       char *subj = get_url_param(h, "subj");  // subject
-       if (!subj) subj = "";
-
-       // Mode 4 will give us metadata back after upload
-       ctdl_printf(c, "ENT0 1|||4|%s||1|||||%s|", subj, wefw);
-       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;
-       }
-
-       // Remember, ctdl_printf() appends \n on its own, so when adding a CRLF newline, only use \r
-       // Or for a blank line, use ctdl_write() with \r\n
-
-       content_type = header_val(h, "Content-type");
-       ctdl_printf(c, "Content-type: %s\r", (content_type ? content_type : "application/octet-stream"));
-       ctdl_write(c, HKEY("\r\n"));
-       ctdl_write(c, h->request_body, h->request_body_length);
-       if (h->request_body[h->request_body_length] != '\n') {
-               ctdl_write(c, HKEY("\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", (int) bytes, (int) 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");
-}
diff --git a/webcit-ng/request.c b/webcit-ng/request.c
deleted file mode 100644 (file)
index 605520e..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-// 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-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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");
-}
-
-
-// Succeed with no output
-void do_204(struct http_transaction *h) {
-       h->response_code = 204;
-       h->response_string = strdup("No content");
-}
-
-
-// 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 URL.
-       // 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->url)) {       // 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->url, "/")) && (!strcasecmp(h->method, "GET"))) {
-               http_redirect(h, "/ctdl/s/index.html");
-               return;
-       }
-
-       // Legacy URL patterns (/readnew?gotoroom=xxx&start_reading_at=yyy) ...
-       // Direct room name (/my%20blog) ...
-
-       // HTTP-01 challenge [RFC5785 section 3, RFC8555 section 9.2]
-       if (!strncasecmp(h->url, HKEY("/.well-known"))) {       // Static content
-               output_static(h);
-               return;
-       }
-
-       if (!strcasecmp(h->url, "/favicon.ico")) {
-               output_static(h);
-               return;
-       }
-
-       // Everything below this line is strictly REST URL patterns.
-
-       if (strncasecmp(h->url, HKEY("/ctdl/"))) {              // Reject non-REST
-               do_404(h);
-               return;
-       }
-
-       if (!strncasecmp(h->url, HKEY("/ctdl/s/"))) {           // Static content
-               output_static(h);
-               return;
-       }
-
-       if (h->url[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 URL by path and send the request to the appropriate part of the program.
-       switch (h->url[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 'f':               // /ctdl/f/ == RESTful path to floors
-               ctdl_f(h, c);
-               break;
-       case 'r':               // /ctdl/r/ == RESTful path to rooms
-               ctdl_r(h, c);
-               break;
-       case 'u':               // /ctdl/u/ == RESTful path to users
-               ctdl_u(h, c);
-               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); // warn
-               free(exp);
-               add_response_header(h, strdup("Set-Cookie"), strdup(koekje));
-       }
-
-       // Durlng 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);
-}
diff --git a/webcit-ng/room_functions.c b/webcit-ng/room_functions.c
deleted file mode 100644 (file)
index 67b3e90..0000000
+++ /dev/null
@@ -1,679 +0,0 @@
-// Room functions
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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, "*")) {                        // wildcard match
-                       return (1);
-               }
-               long tagmsgnum = atol(tag);
-               if ((tagmsgnum > 0) && (tagmsgnum == msgnum)) { // match
-                       return (1);
-               }
-       }
-
-       return (0);                                             // no match
-}
-
-
-// Client is requesting a STAT (name and modification time) of the current room
-void json_stat(struct http_transaction *h, struct ctdlsession *c) {
-       char buf[1024];
-       char field[1024];
-
-       ctdl_printf(c, "STAT");
-       ctdl_readline(c, buf, sizeof(buf));
-       if (buf[0] == '2') {
-               JsonValue *j = NewJsonObject(HKEY("stat"));
-               extract_token(field, &buf[4], 0, '|', sizeof field);
-               JsonObjectAppend(j, NewJsonPlainString(HKEY("name"), field, -1));
-               JsonObjectAppend(j, NewJsonNumber(HKEY("room_mtime"), extract_long(&buf[4], 1)));
-
-               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);
-       }
-       else {
-               do_404(h);
-       }
-       return;
-}
-
-
-// Client is requesting a mailbox summary of the current room
-void json_mailbox(struct http_transaction *h, struct ctdlsession *c) {
-       char buf[1024];
-       char field[1024];
-       JsonValue *j = NewJsonArray(HKEY("msgs"));
-
-       ctdl_printf(c, "MSGS ALL|||9");                         // "9" as the fourth parameter delivers a mailbox summary
-       ctdl_readline(c, buf, sizeof(buf));
-       if (buf[0] == '1') {
-               while (ctdl_readline(c, buf, sizeof(buf)), (strcmp(buf, "000"))) {
-                       utf8ify_rfc822_string(buf);
-                       JsonValue *jmsg = NewJsonObject(HKEY("message"));
-                       JsonObjectAppend(jmsg, NewJsonNumber(HKEY("msgnum"), extract_long(buf, 0)));
-                       JsonObjectAppend(jmsg, NewJsonNumber(HKEY("time"), extract_long(buf, 1)));
-                       extract_token(field, buf, 2, '|', sizeof field);
-                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("author"), field, -1));
-                       extract_token(field, buf, 4, '|', sizeof field);
-                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("addr"), field, -1));
-                       extract_token(field, buf, 5, '|', sizeof field);
-                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("subject"), field, -1));
-                       JsonObjectAppend(jmsg, NewJsonNumber(HKEY("msgidhash"), extract_long(buf, 6)));
-                       extract_token(field, buf, 7, '|', sizeof field);
-                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("references"), field, -1));
-                       JsonArrayAppend(j, jmsg);               // add the message 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);
-       return;
-}
-
-
-// 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 is requesting the room info banner
-void read_room_info_banner(struct http_transaction *h, struct ctdlsession *c) {
-       char buf[1024];
-
-       ctdl_printf(c, "RINF");
-       ctdl_readline(c, buf, sizeof(buf));
-       if (buf[0] == '1') {
-               StrBuf *info = NewStrBuf();
-               while (ctdl_readline(c, buf, sizeof(buf)), (strcmp(buf, "000"))){
-                       StrBufAppendPrintf(info, "%s\n", buf);
-               }
-               add_response_header(h, strdup("Content-type"), strdup("text/plain"));
-               h->response_code = 200;
-               h->response_string = strdup("OK");
-               h->response_body_length = StrLength(info);
-               h->response_body = SmashStrBuf(&info);
-               return;
-       }
-       else {
-               do_404(h);
-       }
-}
-
-
-// Client is setting the "Last Read Pointer" (marking all messages as "seen" up to this message)
-void set_last_read_pointer(struct http_transaction *h, struct ctdlsession *c) {
-       char cbuf[1024];
-       ctdl_printf(c, "SLRP %d", atoi(get_url_param(h, "last")));
-       ctdl_readline(c, cbuf, sizeof(cbuf));
-       if (cbuf[0] == '2') {
-               do_204(h);
-       }
-       else {
-               do_404(h);
-       }
-}
-
-
-// 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->url, 4, '/', sizeof buf);
-
-       if (!strcasecmp(buf, "mailbox")) {              // Client is requesting a mailbox summary
-               json_mailbox(h, c);
-               return;
-       }
-
-       if (!strcasecmp(buf, "stat")) {                 // Client is requesting a stat command (name and modification time)
-               json_stat(h, c);
-               return;
-       }
-
-       if (!strncasecmp(buf, "msgs.", 5)) {            // Client is requesting a list of message numbers
-               unescape_input(&buf[5]);
-               json_msglist(h, c, &buf[5]);
-               return;
-       }
-
-       if (!strcasecmp(buf, "info.txt")) {             // Client is requesting the room info banner
-               read_room_info_banner(h, c);
-               return;
-       }
-
-       if (!strcasecmp(buf, "slrp")) {                 // Set the Last Read Pointer
-               set_last_read_pointer(h, c);
-               return;
-       }
-
-       // If we get to this point, the client is requesting a specific object *in* the room.
-
-       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 {                                          // otherwise the object is being referenced by message number
-               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);
-
-       // A sixth component in the URL can be one of two things:
-       // (1) a MIME part specifier, in which case the client wants to download that component within the message
-       // (2) a content-type, in which ase the client wants us to try to render it a certain way
-       if (num_tokens(h->url, '/') == 6) {
-               extract_token(buf, h->url, 5, '/', sizeof buf);
-               if (!IsEmptyStr(buf)) {
-                       if (!strcasecmp(buf, "json")) {
-                               json_render_one_message(h, c, msgnum);
-                       }
-                       else {
-                               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" ?  The versioning stuff in DAV could be useful.
-               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>");
-                               StrBufAppendPrintf(Buf, "<D:propstat>");
-                               StrBufAppendPrintf(Buf, "<D:status>HTTP/1.1 200 OK</D:status>");
-                               StrBufAppendPrintf(Buf, "<D:prop>");
-
-                               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;
-                               }
-
-                               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) {        // FIXME ajc 2017oct30 should this be inside the timestamp conditional?
-                                               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("is_room_aide"), c->is_room_aide));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("can_delete_messages"), c->can_delete_messages));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("new_messages"), c->new_messages));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("total_messages"), c->total_messages));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("last_seen"), c->last_seen));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("room_mtime"), c->room_mtime));
-       JsonObjectAppend(j, NewJsonNumber(HKEY("new_mail"), c->new_mail));
-
-       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")) {
-
-               // 0   |1      |2      |3      |4       |5 |6           |7           |8
-               // 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));
-
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("floor"), extract_int(buf, 2)));
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("rorder"), extract_int(buf, 3)));
-
-               int ra = extract_int(buf, 5);
-               JsonObjectAppend(jr, NewJsonBool(HKEY("known"), (ra & UA_KNOWN)));
-               JsonObjectAppend(jr, NewJsonBool(HKEY("hasnewmsgs"), (ra & UA_HASNEWMSGS)));
-
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("current_view"), extract_int(buf, 6)));
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("default_view"), extract_int(buf, 7)));
-               JsonObjectAppend(jr, NewJsonNumber(HKEY("mtime"), extract_int(buf, 8)));
-
-               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];
-       long room_flags = 0;
-       long room_flags2 = 0;
-
-       // All room-related functions require being "in" the room specified.  Are we in that room already?
-       extract_token(requested_roomname, h->url, 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
-                       room_flags = extract_long(&buf[4], 3);          // Various flags associated with this room.
-                       //      5       (long)CC->room.QRhighest        The highest message number present in this room
-                       c->last_seen = extract_long(&buf[4], 6);        // 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.
-                       c->is_room_aide = extract_int(&buf[4], 8);
-                       c->new_mail = extract_int(&buf[4], 9);          // the number of new messages in the user's INBOX
-                       //      10      (int)CC->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.
-                       room_flags2 = extract_long(&buf[4], 14);        // More flags associated with this room.
-                       c->room_mtime = extract_long(&buf[4], 15);      // Timestamp of the last write activity in this room
-
-                       // If any of these three conditions are met, let the client know it has permission to delete messages.
-                       if ((c->is_room_aide) || (room_flags & QR_MAILBOX) || (room_flags2 & QR2_COLLABDEL)) {
-                               c->can_delete_messages = 1;
-                       }
-                       else {
-                               c->can_delete_messages = 0;
-                       }
-               }
-               else {
-                       do_404(h);
-                       return;
-               }
-       }
-       // At this point our Citadel client session is "in" the specified room.
-
-       if (num_tokens(h->url, '/') == 4) {     //      /ctdl/r/roomname
-               the_room_itself(h, c);
-               return;
-       }
-
-       extract_token(buf, h->url, 4, '/', sizeof buf);
-       if (num_tokens(h->url, '/') == 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->url, '/') == 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);
-}
diff --git a/webcit-ng/server/admin_functions.c b/webcit-ng/server/admin_functions.c
new file mode 100644 (file)
index 0000000..2c2d85f
--- /dev/null
@@ -0,0 +1,105 @@
+// Admin functions
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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), BASE64_NO_LINEBREAKS);
+
+       syslog(LOG_DEBUG, "try_login(username='%s',password=(%d bytes))", username, (int) 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
+
+       JsonValue *j = NewJsonObject(HKEY("login"));                            // Compose a JSON object with the results
+       if (buf[0] == '2') {
+               JsonObjectAppend(j, NewJsonBool(HKEY("result"), 1));
+               JsonObjectAppend(j, NewJsonPlainString(HKEY("message"), "logged in", -1));
+               extract_token(username, &buf[4], 0, '|', sizeof username);      // This will have the proper capitalization etc.
+               JsonObjectAppend(j, NewJsonPlainString(HKEY("fullname"), username, -1));
+               JsonObjectAppend(j, NewJsonNumber(HKEY("axlevel"), extract_int(&buf[4], 1) ));
+               JsonObjectAppend(j, NewJsonNumber(HKEY("timescalled"), extract_long(&buf[4], 2) ));
+               JsonObjectAppend(j, NewJsonNumber(HKEY("posted"), extract_long(&buf[4], 3) ));
+               JsonObjectAppend(j, NewJsonNumber(HKEY("usernum"), extract_long(&buf[4], 5) ));
+               JsonObjectAppend(j, NewJsonNumber(HKEY("previous_login"), extract_long(&buf[4], 6) ));
+       }
+       else {
+               JsonObjectAppend(j, NewJsonBool(HKEY("result"), 0));
+               JsonObjectAppend(j, NewJsonPlainString(HKEY("message"), &buf[4], -1));
+       }
+       StrBuf *sj = NewStrBuf();
+       SerializeJson(sj, j, 1);                                                // '1' == free the source object
+
+       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);
+}
+
+
+// /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->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->url, "/ctdl/a/login")) {     // log in
+               try_login(h, c);
+               return;
+       }
+
+       if (!strcasecmp(h->url, "/ctdl/a/logout")) {    // log out
+               logout(h, c);
+               return;
+       }
+
+       if (!strcasecmp(h->url, "/ctdl/a/whoami")) {    // return display name of user
+               whoami(h, c);
+               return;
+       }
+
+       do_404(h);                                      // unknown
+}
diff --git a/webcit-ng/server/caldav_reports.c b/webcit-ng/server/caldav_reports.c
new file mode 100644 (file)
index 0000000..9bcca61
--- /dev/null
@@ -0,0 +1,270 @@
+//
+// This file contains functions which handle all of the CalDAV "REPORT" queries
+// specified in RFC4791 section 7.
+//
+// Copyright (c) 2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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.
+void 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);
+}
diff --git a/webcit-ng/server/ctdl_commands.c b/webcit-ng/server/ctdl_commands.c
new file mode 100644 (file)
index 0000000..6420a87
--- /dev/null
@@ -0,0 +1,94 @@
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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->url, "/ctdl/c/info")) {
+               serv_info(h, c);
+       }
+       else {
+               do_404(h);
+       }
+}
diff --git a/webcit-ng/server/ctdlclient.c b/webcit-ng/server/ctdlclient.c
new file mode 100644 (file)
index 0000000..fa35595
--- /dev/null
@@ -0,0 +1,311 @@
+// Functions that handle communication with a Citadel Server
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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 specific number of bytes of binary data from the Citadel server.
+// Returns the number of bytes read or -1 for error.
+int ctdl_read_binary(struct ctdlsession *ctdl, char *buf, int bytes_requested) {
+       int bytes_read = 0;
+       int c = 0;
+
+       while (bytes_read < bytes_requested) {
+               c = read(ctdl->sock, &buf[bytes_read], bytes_requested-bytes_read);
+               if (c <= 0) {
+                       syslog(LOG_DEBUG, "Socket error or zero-length read");
+                       return (-1);
+               }
+               bytes_read += c;
+       }
+       return (bytes_read);
+}
+
+
+// Read a newline-terminated line of text from the Citadel server.
+// 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 (-1);
+       }
+
+       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, "\033[32m[ %s\033[0m", buf);
+                       return (len);
+               }
+               ++len;
+       }
+       // syslog(LOG_DEBUG, "\033[32m[ %s\033[0m", 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)), 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, "\033[32m] %s\033[0m", 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;
+}
+
+
+// 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;
+       }
+
+       memset(authbuf, 0, authbuflen);
+
+       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, password=(%d bytes)", 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') {
+               extract_token(c->whoami, &buf[4], 0, '|', sizeof c->whoami);
+               syslog(LOG_DEBUG, "Logged in as %s", c->whoami);
+
+               // Re-encode the auth string so it contains the properly formatted username
+               char new_auth_string[1024];
+               snprintf(new_auth_string, sizeof(new_auth_string),  "%s:%s", c->whoami, supplied_password);
+               CtdlEncodeBase64(c->auth, new_auth_string, strlen(new_auth_string), BASE64_NO_LINEBREAKS);
+
+               return(0);
+       }
+
+       syslog(LOG_DEBUG, "Login failed: %s", &buf[4]);
+       return(1);              // login failed; resultbuf will explain why
+}
+
+
+// This is a variant of the "server connection pool" design pattern.  We go through our list
+// of connections to Citadel Server, looking for a connection that is at once:
+// 1. Not currently serving a WebCit transaction (is_bound)
+// 2a. Is logged in to Citadel as the correct user, if the HTTP session is logged in; or
+// 2b. Is NOT logged in to Citadel, if the HTTP session is not logged in.
+// If we find a qualifying connection, we bind to it for the duration of this WebCit HTTP transaction.
+// Otherwise, we create a new connection to Citadel Server and add it to the pool.
+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);
+
+       // 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);                                           // Could not create a new session (yikes!)
+       }
+
+       if (my_session->sock < 3) {
+               is_new_session = 1;
+       }
+       else {                                                          // make sure our Citadel session is still working
+               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, "");
+               static char *ctdl_sock_path = NULL;
+               if (!ctdl_sock_path) {
+                       ctdl_sock_path = malloc(PATH_MAX);
+                       snprintf(ctdl_sock_path, PATH_MAX, "%s/citadel.socket", ctdl_dir);
+               }
+               my_session->sock = uds_connectsock(ctdl_sock_path);
+               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);
+}
diff --git a/webcit-ng/server/ctdlfunctions.c b/webcit-ng/server/ctdlfunctions.c
new file mode 100644 (file)
index 0000000..ba25fd6
--- /dev/null
@@ -0,0 +1,36 @@
+//
+// These utility functions loosely make up a Citadel protocol client library.
+//
+// Copyright (c) 2016-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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);
+}
diff --git a/webcit-ng/server/floor_functions.c b/webcit-ng/server/floor_functions.c
new file mode 100644 (file)
index 0000000..ccb6326
--- /dev/null
@@ -0,0 +1,56 @@
+//
+// Floor functions
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#include "webcit.h"
+
+
+// Dispatcher for "/ctdl/f" and "/ctdl/f/" for the floor list
+void floor_list(struct http_transaction *h, struct ctdlsession *c) {
+       char buf[1024];
+       char floorname[1024];
+
+       ctdl_printf(c, "LFLR");
+       ctdl_readline(c, buf, sizeof(buf));
+       if (buf[0] != '1') {
+               do_502(h);
+               return;
+       }
+
+       JsonValue *j = NewJsonArray(HKEY("lflr"));
+       while (ctdl_readline(c, buf, sizeof(buf)), strcmp(buf, "000")) {
+
+               // 0  |1   |2
+               // num|name|refcount
+               JsonValue *jr = NewJsonObject(HKEY("floor"));
+
+               extract_token(floorname, buf, 1, '|', sizeof floorname);
+               JsonObjectAppend(jr, NewJsonPlainString(HKEY("name"), floorname, -1));
+
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("num"), extract_int(buf, 0)));
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("refcount"), extract_int(buf, 2)));
+
+               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/f/
+// (This is a stub ... we will need to add more functions when we can do more than just a floor list)
+void ctdl_f(struct http_transaction *h, struct ctdlsession *c) {
+       floor_list(h, c);
+       return;
+}
diff --git a/webcit-ng/server/forum_view.c b/webcit-ng/server/forum_view.c
new file mode 100644 (file)
index 0000000..90ef337
--- /dev/null
@@ -0,0 +1,148 @@
+// The code in here feeds messages out as JSON to the client browser.  It is currently being used
+// for the forum view, but as we implement other views we'll probably reuse a lot of what's here.
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#include "webcit.h"
+
+// Commands we need to send to Citadel Server before we begin rendering forum view.
+// These are common to flat and threaded views.
+void setup_for_forum_view(struct ctdlsession *c) {
+       char buf[1024];
+       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
+}
+
+
+// Fetch a single message and return it in JSON format for client-side rendering
+void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum) {
+       StrBuf *raw_msg = NULL;
+       StrBuf *sanitized_msg = NULL;
+       char buf[1024];
+       char content_transfer_encoding[1024] = { 0 };
+       char content_type[1024] = { 0 };
+       char datetime[128] = { 0 };
+       char author[1024] = { 0 };
+       char emailaddr[1024] = { 0 };
+       int message_originated_locally = 0;
+
+       setup_for_forum_view(c);
+
+       ctdl_printf(c, "MSG4 %ld", msgnum);
+       ctdl_readline(c, buf, sizeof(buf));
+       if (buf[0] != '1') {
+               do_404(h);
+               return;
+       }
+
+       JsonValue *j = NewJsonObject(HKEY("message"));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("msgnum"), msgnum));
+
+       while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000"))) {
+               utf8ify_rfc822_string(&buf[5]);
+
+               // citadel header parsing here
+               if (!strncasecmp(buf, "from=", 5)) {
+                       safestrncpy(author, &buf[5], sizeof author);
+               }
+               else if (!strncasecmp(buf, "rfca=", 5)) {
+                       safestrncpy(emailaddr, &buf[5], sizeof emailaddr);
+               }
+               else if (!strncasecmp(buf, "time=", 5)) {
+                       JsonObjectAppend(j, NewJsonNumber(HKEY("time"), atol(&buf[5])));
+               }
+               else if (!strncasecmp(buf, "locl=", 5)) {
+                       message_originated_locally = 1;
+               }
+               else if (!strncasecmp(buf, "subj=", 5)) {
+                       JsonObjectAppend(j, NewJsonPlainString(HKEY("subj"), &buf[5], -1));
+               }
+               else if (!strncasecmp(buf, "msgn=", 5)) {
+                       JsonObjectAppend(j, NewJsonPlainString(HKEY("msgn"), &buf[5], -1));
+               }
+               else if (!strncasecmp(buf, "wefw=", 5)) {
+                       JsonObjectAppend(j, NewJsonPlainString(HKEY("wefw"), &buf[5], -1));
+               }
+       }
+
+       if (message_originated_locally) {
+               JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), author, -1));
+       }
+       else {
+               JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), emailaddr, -1));           // FIXME do compound address string
+       }
+
+       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);
+                       }
+               }
+               if (!strcmp(buf, "000")) {              // if we have an empty message, don't try to read further
+                       raw_msg = NULL;
+               }
+               else {
+                       raw_msg = ctdl_readtextmsg(c);
+               }
+       }
+       else {
+               raw_msg = NULL;
+       }
+
+       if (raw_msg) {
+               // These are the encodings we know how to handle.  Decode in-place.
+
+               if (!strcasecmp(content_transfer_encoding, "base64")) {
+                       StrBufDecodeBase64(raw_msg);
+               }
+               if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
+                       StrBufDecodeQP(raw_msg);
+               }
+
+               // At this point, raw_msg contains the decoded message.
+               // Now run through the renderers we have available.
+
+               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>"));
+                       syslog(LOG_WARNING, "forum_view: no renderer for content type %s", content_type);
+               }
+               FreeStrBuf(&raw_msg);
+
+               // If sanitized_msg is not NULL, we have rendered the message and can output it.
+               if (sanitized_msg) {
+                       JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
+               }
+               else {
+                       syslog(LOG_WARNING, "forum_view: message %ld of content type %s converted to NULL", msgnum, content_type);
+               }
+       }
+
+       StrBuf *sj = NewStrBuf();
+       SerializeJson(sj, j, 1);        // '1' == free the source object
+
+       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);
+}
diff --git a/webcit-ng/server/html2html.c b/webcit-ng/server/html2html.c
new file mode 100644 (file)
index 0000000..b6dadc3
--- /dev/null
@@ -0,0 +1,635 @@
+//
+// Output an HTML message, modifying it slightly to make sure it plays nice
+// with the rest of our web framework.
+//
+// Copyright (c) 2005-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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
+
+       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.
+               // FIXME change URL syntax for webcit-ng
+               if (!strncasecmp(ptr, "<a href=\"mailto:", 16)) {
+                       content_length += 64;
+                       StrBufAppendPrintf(converted_msg, "<a href=\"display_enter?force_room=_MAIL_?recp=");
+                       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/<msgnum>/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, "&nbsp;");
+                               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';
+}
diff --git a/webcit-ng/server/http.c b/webcit-ng/server/http.c
new file mode 100644 (file)
index 0000000..55c335e
--- /dev/null
@@ -0,0 +1,327 @@
+// This module handles HTTP transactions.
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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 (-1);
+       }
+
+       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 keyval new_response_header;
+       new_response_header.key = key;
+       new_response_header.val = val;
+       array_append(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;
+       int i;                                  // general purpose iterator variable
+
+       memset(&h, 0, sizeof h);
+       h.request_headers = array_new(sizeof(struct keyval));
+       h.request_parms = array_new(sizeof(struct keyval));
+       h.response_headers = array_new(sizeof(struct keyval));
+
+       while (len = client_readline(ch, buf, sizeof buf), (len > 0)) {
+               ++lines_read;
+
+               if (lines_read == 1) {          // First line is method and URL.
+                       c = strchr(buf, ' ');   // IGnore the HTTP-version.
+                       if (c == NULL) {
+                               h.method = strdup("NULL");
+                               h.url = strdup("/");
+                       }
+                       else {
+                               *c = 0;
+                               h.method = strdup(buf);
+                               ++c;
+                               d = c;
+                               c = strchr(d, ' ');
+                               if (c != NULL) {
+                                       *c = 0;
+                               }
+                               ++c;
+                               h.url = 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 keyval new_request_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);
+                               array_append(h.request_headers, &new_request_header);
+#ifdef DEBUG_HTTP
+                               syslog(LOG_DEBUG, "\033[1m\033[35m{ %s: %s\033[0m", new_request_header.key, new_request_header.val);
+#endif
+                       }
+               }
+
+       }
+
+       // If the URL had any query parameters in it, parse them out now.
+       char *p = (h.url ? strchr(h.url, '?') : NULL);
+       if (p) {
+               *p++ = 0;                                               // insert a null to remove parameters from the URL
+               char *tok, *saveptr = NULL;
+               for (tok = strtok_r(p, "&", &saveptr); tok!=NULL; tok = strtok_r(NULL, "&", &saveptr)) {
+                       char *eq = strchr(tok, '=');
+                       if (eq) {
+                               *eq++ = 0;
+                               unescape_input(eq);
+                               struct keyval kv;
+                               kv.key = strdup(tok);
+                               kv.val = strdup(eq);
+                               array_append(h.request_parms, &kv);
+#ifdef DEBUG_HTTP
+                               syslog(LOG_DEBUG, "\033[1m\033[33m| %s = %s\033[0m", kv.key, kv.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 {
+//#ifdef DEBUG_HTTP
+               syslog(LOG_DEBUG, "\033[33m\033[1m< %s %s\033[0m", h.method, h.url);
+//#endif
+
+               // 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 the entire request body to stderr -- not what you want during normal operation.
+                       #ifdef BODY_TO_STDERR
+                       write(2, HKEY("\033[31m"));
+                       write(2, h.request_body, h.request_body_length);
+                       write(2, HKEY("\033[0m\n"));
+                       #endif
+
+               }
+
+               // Now pass control up to the next layer to perform the request.
+               perform_request(&h);
+
+               // Write the entire response body to stderr -- not what you want during normal operation.
+               #ifdef BODY_TO_STDERR
+               write(2, HKEY("\033[32m"));
+               write(2, h.response_body, h.response_body_length);
+               write(2, HKEY("\033[0m\n"));
+               #endif
+
+               // Output the results back to the client.
+#ifdef DEBUG_HTTP
+               syslog(LOG_DEBUG, "\033[33m\033[1m> %03d %s\033[0m", h.response_code, h.response_string);
+#endif
+               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
+               int number_of_response_headers = array_len(h.response_headers);
+               for (i=0; i<number_of_response_headers; ++i) {
+                       struct keyval *kv = array_get_element_at(h.response_headers, i);
+#ifdef DEBUG_HTTP
+                       syslog(LOG_DEBUG, "\033[1m\033[35m} %s: %s\033[0m", kv->key, kv->val);
+#endif
+                       client_printf(ch, "%s: %s\r\n", kv->key, kv->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 (array_len(h.request_headers) > 0) {
+               struct keyval *kv = array_get_element_at(h.request_headers, 0);
+               if (kv->key) free(kv->key);
+               if (kv->val) free(kv->val);
+               array_delete_element_at(h.request_headers, 0);
+       }
+       array_free(h.request_headers);
+       while (array_len(h.request_parms) > 0) {
+               struct keyval *kv = array_get_element_at(h.request_parms, 0);
+               if (kv->key) free(kv->key);
+               if (kv->val) free(kv->val);
+               array_delete_element_at(h.request_parms, 0);
+       }
+       array_free(h.request_parms);
+       while (array_len(h.response_headers) > 0) {
+               struct keyval *kv = array_get_element_at(h.response_headers, 0);
+               if (kv->key) free(kv->key);
+               if (kv->val) free(kv->val);
+               array_delete_element_at(h.response_headers, 0);
+       }
+       array_free(h.response_headers);
+       if (h.method) {
+               free(h.method);
+       }
+       if (h.url) {
+               free(h.url);
+       }
+       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 keyval *kv;
+       int i;
+       for (i=0; i<array_len(h->request_headers); ++i) {
+               kv = array_get_element_at(h->request_headers, i);
+               if (!strcasecmp(kv->key, requested_header)) {
+                       return (kv->val);
+               }
+       }
+       return (NULL);
+}
+
+
+// Utility function to fetch a specific URL parameter from a completely read-in request.
+// Returns the value of the requested parameter, or NULL if the specified parameter 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 *get_url_param(struct http_transaction *h, char *requested_param) {
+       struct keyval *kv;
+       int i;
+       for (i=0; i<array_len(h->request_parms); ++i) {
+               kv = array_get_element_at(h->request_parms, i);
+               if (!strcasecmp(kv->key, requested_param)) {
+                       return (kv->val);
+               }
+       }
+       return (NULL);
+}
diff --git a/webcit-ng/server/main.c b/webcit-ng/server/main.c
new file mode 100644 (file)
index 0000000..0313691
--- /dev/null
@@ -0,0 +1,129 @@
+// Main entry point for the program.
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#include "webcit.h"            // All other headers are included from this header.
+
+char *ctdl_dir = CTDL_DIR;
+
+// 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':
+                       ctdl_dir = strdup(optarg);
+                       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]"
+                               "[-h citadel_server_directory]\n"
+                       );
+                       return 1;
+               }
+
+       while (optind < argc) {
+               ctdl_dir = strdup(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-2022 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(1, 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);
+}
diff --git a/webcit-ng/server/messages.c b/webcit-ng/server/messages.c
new file mode 100644 (file)
index 0000000..1ef039f
--- /dev/null
@@ -0,0 +1,264 @@
+//
+// Message base functions
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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;
+       }
+
+       char *wefw = get_url_param(h, "wefw");  // references
+       if (!wefw) wefw = "";
+       char *subj = get_url_param(h, "subj");  // subject
+       if (!subj) subj = "";
+
+       // Mode 4 will give us metadata back after upload
+       ctdl_printf(c, "ENT0 1|||4|%s||1|||||%s|", subj, wefw);
+       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;
+       }
+
+       // Remember, ctdl_printf() appends \n on its own, so when adding a CRLF newline, only use \r
+       // Or for a blank line, use ctdl_write() with \r\n
+
+       content_type = header_val(h, "Content-type");
+       ctdl_printf(c, "Content-type: %s\r", (content_type ? content_type : "application/octet-stream"));
+       ctdl_write(c, HKEY("\r\n"));
+       ctdl_write(c, h->request_body, h->request_body_length);
+       if (h->request_body[h->request_body_length] != '\n') {
+               ctdl_write(c, HKEY("\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", (int) bytes, (int) 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");
+}
diff --git a/webcit-ng/server/request.c b/webcit-ng/server/request.c
new file mode 100644 (file)
index 0000000..605520e
--- /dev/null
@@ -0,0 +1,183 @@
+// 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-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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");
+}
+
+
+// Succeed with no output
+void do_204(struct http_transaction *h) {
+       h->response_code = 204;
+       h->response_string = strdup("No content");
+}
+
+
+// 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 URL.
+       // 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->url)) {       // 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->url, "/")) && (!strcasecmp(h->method, "GET"))) {
+               http_redirect(h, "/ctdl/s/index.html");
+               return;
+       }
+
+       // Legacy URL patterns (/readnew?gotoroom=xxx&start_reading_at=yyy) ...
+       // Direct room name (/my%20blog) ...
+
+       // HTTP-01 challenge [RFC5785 section 3, RFC8555 section 9.2]
+       if (!strncasecmp(h->url, HKEY("/.well-known"))) {       // Static content
+               output_static(h);
+               return;
+       }
+
+       if (!strcasecmp(h->url, "/favicon.ico")) {
+               output_static(h);
+               return;
+       }
+
+       // Everything below this line is strictly REST URL patterns.
+
+       if (strncasecmp(h->url, HKEY("/ctdl/"))) {              // Reject non-REST
+               do_404(h);
+               return;
+       }
+
+       if (!strncasecmp(h->url, HKEY("/ctdl/s/"))) {           // Static content
+               output_static(h);
+               return;
+       }
+
+       if (h->url[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 URL by path and send the request to the appropriate part of the program.
+       switch (h->url[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 'f':               // /ctdl/f/ == RESTful path to floors
+               ctdl_f(h, c);
+               break;
+       case 'r':               // /ctdl/r/ == RESTful path to rooms
+               ctdl_r(h, c);
+               break;
+       case 'u':               // /ctdl/u/ == RESTful path to users
+               ctdl_u(h, c);
+               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); // warn
+               free(exp);
+               add_response_header(h, strdup("Set-Cookie"), strdup(koekje));
+       }
+
+       // Durlng 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);
+}
diff --git a/webcit-ng/server/room_functions.c b/webcit-ng/server/room_functions.c
new file mode 100644 (file)
index 0000000..67b3e90
--- /dev/null
@@ -0,0 +1,679 @@
+// Room functions
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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, "*")) {                        // wildcard match
+                       return (1);
+               }
+               long tagmsgnum = atol(tag);
+               if ((tagmsgnum > 0) && (tagmsgnum == msgnum)) { // match
+                       return (1);
+               }
+       }
+
+       return (0);                                             // no match
+}
+
+
+// Client is requesting a STAT (name and modification time) of the current room
+void json_stat(struct http_transaction *h, struct ctdlsession *c) {
+       char buf[1024];
+       char field[1024];
+
+       ctdl_printf(c, "STAT");
+       ctdl_readline(c, buf, sizeof(buf));
+       if (buf[0] == '2') {
+               JsonValue *j = NewJsonObject(HKEY("stat"));
+               extract_token(field, &buf[4], 0, '|', sizeof field);
+               JsonObjectAppend(j, NewJsonPlainString(HKEY("name"), field, -1));
+               JsonObjectAppend(j, NewJsonNumber(HKEY("room_mtime"), extract_long(&buf[4], 1)));
+
+               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);
+       }
+       else {
+               do_404(h);
+       }
+       return;
+}
+
+
+// Client is requesting a mailbox summary of the current room
+void json_mailbox(struct http_transaction *h, struct ctdlsession *c) {
+       char buf[1024];
+       char field[1024];
+       JsonValue *j = NewJsonArray(HKEY("msgs"));
+
+       ctdl_printf(c, "MSGS ALL|||9");                         // "9" as the fourth parameter delivers a mailbox summary
+       ctdl_readline(c, buf, sizeof(buf));
+       if (buf[0] == '1') {
+               while (ctdl_readline(c, buf, sizeof(buf)), (strcmp(buf, "000"))) {
+                       utf8ify_rfc822_string(buf);
+                       JsonValue *jmsg = NewJsonObject(HKEY("message"));
+                       JsonObjectAppend(jmsg, NewJsonNumber(HKEY("msgnum"), extract_long(buf, 0)));
+                       JsonObjectAppend(jmsg, NewJsonNumber(HKEY("time"), extract_long(buf, 1)));
+                       extract_token(field, buf, 2, '|', sizeof field);
+                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("author"), field, -1));
+                       extract_token(field, buf, 4, '|', sizeof field);
+                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("addr"), field, -1));
+                       extract_token(field, buf, 5, '|', sizeof field);
+                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("subject"), field, -1));
+                       JsonObjectAppend(jmsg, NewJsonNumber(HKEY("msgidhash"), extract_long(buf, 6)));
+                       extract_token(field, buf, 7, '|', sizeof field);
+                       JsonObjectAppend(jmsg, NewJsonPlainString(HKEY("references"), field, -1));
+                       JsonArrayAppend(j, jmsg);               // add the message 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);
+       return;
+}
+
+
+// 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 is requesting the room info banner
+void read_room_info_banner(struct http_transaction *h, struct ctdlsession *c) {
+       char buf[1024];
+
+       ctdl_printf(c, "RINF");
+       ctdl_readline(c, buf, sizeof(buf));
+       if (buf[0] == '1') {
+               StrBuf *info = NewStrBuf();
+               while (ctdl_readline(c, buf, sizeof(buf)), (strcmp(buf, "000"))){
+                       StrBufAppendPrintf(info, "%s\n", buf);
+               }
+               add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+               h->response_code = 200;
+               h->response_string = strdup("OK");
+               h->response_body_length = StrLength(info);
+               h->response_body = SmashStrBuf(&info);
+               return;
+       }
+       else {
+               do_404(h);
+       }
+}
+
+
+// Client is setting the "Last Read Pointer" (marking all messages as "seen" up to this message)
+void set_last_read_pointer(struct http_transaction *h, struct ctdlsession *c) {
+       char cbuf[1024];
+       ctdl_printf(c, "SLRP %d", atoi(get_url_param(h, "last")));
+       ctdl_readline(c, cbuf, sizeof(cbuf));
+       if (cbuf[0] == '2') {
+               do_204(h);
+       }
+       else {
+               do_404(h);
+       }
+}
+
+
+// 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->url, 4, '/', sizeof buf);
+
+       if (!strcasecmp(buf, "mailbox")) {              // Client is requesting a mailbox summary
+               json_mailbox(h, c);
+               return;
+       }
+
+       if (!strcasecmp(buf, "stat")) {                 // Client is requesting a stat command (name and modification time)
+               json_stat(h, c);
+               return;
+       }
+
+       if (!strncasecmp(buf, "msgs.", 5)) {            // Client is requesting a list of message numbers
+               unescape_input(&buf[5]);
+               json_msglist(h, c, &buf[5]);
+               return;
+       }
+
+       if (!strcasecmp(buf, "info.txt")) {             // Client is requesting the room info banner
+               read_room_info_banner(h, c);
+               return;
+       }
+
+       if (!strcasecmp(buf, "slrp")) {                 // Set the Last Read Pointer
+               set_last_read_pointer(h, c);
+               return;
+       }
+
+       // If we get to this point, the client is requesting a specific object *in* the room.
+
+       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 {                                          // otherwise the object is being referenced by message number
+               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);
+
+       // A sixth component in the URL can be one of two things:
+       // (1) a MIME part specifier, in which case the client wants to download that component within the message
+       // (2) a content-type, in which ase the client wants us to try to render it a certain way
+       if (num_tokens(h->url, '/') == 6) {
+               extract_token(buf, h->url, 5, '/', sizeof buf);
+               if (!IsEmptyStr(buf)) {
+                       if (!strcasecmp(buf, "json")) {
+                               json_render_one_message(h, c, msgnum);
+                       }
+                       else {
+                               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" ?  The versioning stuff in DAV could be useful.
+               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>");
+                               StrBufAppendPrintf(Buf, "<D:propstat>");
+                               StrBufAppendPrintf(Buf, "<D:status>HTTP/1.1 200 OK</D:status>");
+                               StrBufAppendPrintf(Buf, "<D:prop>");
+
+                               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;
+                               }
+
+                               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) {        // FIXME ajc 2017oct30 should this be inside the timestamp conditional?
+                                               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("is_room_aide"), c->is_room_aide));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("can_delete_messages"), c->can_delete_messages));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("new_messages"), c->new_messages));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("total_messages"), c->total_messages));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("last_seen"), c->last_seen));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("room_mtime"), c->room_mtime));
+       JsonObjectAppend(j, NewJsonNumber(HKEY("new_mail"), c->new_mail));
+
+       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")) {
+
+               // 0   |1      |2      |3      |4       |5 |6           |7           |8
+               // 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));
+
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("floor"), extract_int(buf, 2)));
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("rorder"), extract_int(buf, 3)));
+
+               int ra = extract_int(buf, 5);
+               JsonObjectAppend(jr, NewJsonBool(HKEY("known"), (ra & UA_KNOWN)));
+               JsonObjectAppend(jr, NewJsonBool(HKEY("hasnewmsgs"), (ra & UA_HASNEWMSGS)));
+
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("current_view"), extract_int(buf, 6)));
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("default_view"), extract_int(buf, 7)));
+               JsonObjectAppend(jr, NewJsonNumber(HKEY("mtime"), extract_int(buf, 8)));
+
+               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];
+       long room_flags = 0;
+       long room_flags2 = 0;
+
+       // All room-related functions require being "in" the room specified.  Are we in that room already?
+       extract_token(requested_roomname, h->url, 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
+                       room_flags = extract_long(&buf[4], 3);          // Various flags associated with this room.
+                       //      5       (long)CC->room.QRhighest        The highest message number present in this room
+                       c->last_seen = extract_long(&buf[4], 6);        // 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.
+                       c->is_room_aide = extract_int(&buf[4], 8);
+                       c->new_mail = extract_int(&buf[4], 9);          // the number of new messages in the user's INBOX
+                       //      10      (int)CC->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.
+                       room_flags2 = extract_long(&buf[4], 14);        // More flags associated with this room.
+                       c->room_mtime = extract_long(&buf[4], 15);      // Timestamp of the last write activity in this room
+
+                       // If any of these three conditions are met, let the client know it has permission to delete messages.
+                       if ((c->is_room_aide) || (room_flags & QR_MAILBOX) || (room_flags2 & QR2_COLLABDEL)) {
+                               c->can_delete_messages = 1;
+                       }
+                       else {
+                               c->can_delete_messages = 0;
+                       }
+               }
+               else {
+                       do_404(h);
+                       return;
+               }
+       }
+       // At this point our Citadel client session is "in" the specified room.
+
+       if (num_tokens(h->url, '/') == 4) {     //      /ctdl/r/roomname
+               the_room_itself(h, c);
+               return;
+       }
+
+       extract_token(buf, h->url, 4, '/', sizeof buf);
+       if (num_tokens(h->url, '/') == 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->url, '/') == 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);
+}
diff --git a/webcit-ng/server/static.c b/webcit-ng/server/static.c
new file mode 100644 (file)
index 0000000..632773d
--- /dev/null
@@ -0,0 +1,63 @@
+// Output static content
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#include "webcit.h"
+
+
+// Called from perform_request() to handle static content.
+void output_static(struct http_transaction *h) {
+       char filename[PATH_MAX];
+       struct stat statbuf;
+
+       syslog(LOG_DEBUG, "static: %s", h->url);
+
+       if (!strncasecmp(h->url, "/ctdl/s/", 8)) {
+               snprintf(filename, sizeof filename, "static/%s", &h->url[8]);
+       }
+       else if (!strncasecmp(h->url, "/.well-known/", 13)) {
+               snprintf(filename, sizeof filename, "static/.well-known/%s", &h->url[13]);
+       }
+       else if (!strcasecmp(h->url, "/favicon.ico")) {
+               snprintf(filename, sizeof filename, "static/images/favicon.ico");
+       }
+       else {
+               do_404(h);
+               return;
+       }
+
+       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) {
+               syslog(LOG_DEBUG, "%s: %s", filename, strerror(errno));
+               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);
+       }
+}
diff --git a/webcit-ng/server/tcp_sockets.c b/webcit-ng/server/tcp_sockets.c
new file mode 100644 (file)
index 0000000..5de373a
--- /dev/null
@@ -0,0 +1,185 @@
+//
+// TCP sockets layer
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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);
+}
diff --git a/webcit-ng/server/text2html.c b/webcit-ng/server/text2html.c
new file mode 100644 (file)
index 0000000..1a1bf14
--- /dev/null
@@ -0,0 +1,104 @@
+//
+// Convert text/plain to text/html
+//
+// Copyright (c) 2017-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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);
+}
diff --git a/webcit-ng/server/tls.c b/webcit-ng/server/tls.c
new file mode 100644 (file)
index 0000000..c0e26f0
--- /dev/null
@@ -0,0 +1,207 @@
+// Functions in this module handle SSL encryption when WebCit is running
+// as an HTTPS server.
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#include "webcit.h"
+
+SSL_CTX *ssl_ctx;              // global SSL context
+char key_file[PATH_MAX] = "";
+char cert_file[PATH_MAX] = "";
+char *ssl_cipher_list = DEFAULT_SSL_CIPHER_LIST;
+
+
+// Set the private key and certificate chain for the global SSL Context.
+// This is called during initialization, and can be called again later if the certificate changes.
+void bind_to_key_and_certificate(void) {
+       SSL_CTX *old_ctx, *new_ctx;
+
+       if (!(new_ctx = SSL_CTX_new(SSLv23_server_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(new_ctx, ssl_cipher_list))) {
+               syslog(LOG_WARNING, "SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       if (IsEmptyStr(key_file)) {
+               snprintf(key_file, sizeof key_file, "%s/keys/citadel.key", ctdl_dir);
+       }
+       if (IsEmptyStr(cert_file)) {
+               snprintf(cert_file, sizeof key_file, "%s/keys/citadel.cer", ctdl_dir);
+       }
+
+       syslog(LOG_DEBUG, "crypto: [re]installing key \"%s\" and certificate \"%s\"", key_file, cert_file);
+
+       SSL_CTX_use_certificate_chain_file(new_ctx, cert_file);
+       SSL_CTX_use_PrivateKey_file(new_ctx, key_file, SSL_FILETYPE_PEM);
+
+       if ( !SSL_CTX_check_private_key(new_ctx) ) {
+               syslog(LOG_WARNING, "crypto: cannot install certificate: %s", ERR_reason_error_string(ERR_get_error()));
+       }
+
+       old_ctx = ssl_ctx;
+       ssl_ctx = new_ctx;
+       sleep(1);
+       SSL_CTX_free(old_ctx);
+}
+
+
+// Initialize ssl engine, load certs and initialize openssl internals
+void init_ssl(void) {
+
+       // Initialize the OpenSSL library
+       SSL_load_error_strings();
+       ERR_load_crypto_strings();
+       OpenSSL_add_all_algorithms();
+       SSL_library_init();
+
+       // Now try to bind to the key and certificate.
+       bind_to_key_and_certificate();
+}
+
+
+// Check the modification time of the key and certificate -- reload if they changed
+void update_key_and_cert_if_needed(void) {
+       static time_t previous_mtime = 0;
+       struct stat keystat;
+       struct stat certstat;
+
+       if (stat(key_file, &keystat) != 0) {
+               syslog(LOG_ERR, "%s: %s", key_file, strerror(errno));
+               return;
+       }
+       if (stat(cert_file, &certstat) != 0) {
+               syslog(LOG_ERR, "%s: %s", cert_file, strerror(errno));
+               return;
+       }
+
+       if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) {
+               bind_to_key_and_certificate();
+               previous_mtime = keystat.st_mtime + certstat.st_mtime;
+       }
+}
+
+
+// starts SSL/TLS encryption for the current session.
+void starttls(struct client_handle *ch) {
+       int retval, bits, alg_bits;
+
+       if (!ssl_ctx) {
+               return;
+       }
+
+       // Check the modification time of the key and certificate -- reload if they changed
+       update_key_and_cert_if_needed();
+
+       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) {
+               syslog(LOG_WARNING, "SSL_accept failed: %s", ERR_reason_error_string(ERR_get_error()));
+       }
+       else {
+               syslog(LOG_INFO, "SSL_accept success");
+       }
+       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: %s", ERR_reason_error_string(ERR_get_error()));
+                       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);
+}
diff --git a/webcit-ng/server/user_functions.c b/webcit-ng/server/user_functions.c
new file mode 100644 (file)
index 0000000..0b4548e
--- /dev/null
@@ -0,0 +1,124 @@
+// User functions
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#include "webcit.h"
+
+
+// Fetch a user photo (avatar)
+void fetch_user_photo(struct http_transaction *h, struct ctdlsession *c, char *username) {
+       char buf[1024];
+       int content_length = 0;
+       char content_type[1024];
+       char *image = NULL;
+       int actual_length = 0;
+
+       ctdl_printf(c, "DLUI %s", username);
+       ctdl_readline(c, buf, sizeof(buf));
+       if (buf[0] == '6') {
+               content_length = extract_int(&buf[4], 0);
+               extract_token(content_type, &buf[4], 3, '|', sizeof content_type);
+
+               image = malloc(content_length);
+               if (image == NULL) {
+                       do_502(h);
+                       return;
+               }
+               actual_length = ctdl_read_binary(c, image, content_length);
+
+               add_response_header(h, strdup("Content-type"), strdup(content_type));
+               h->response_code = 200;
+               h->response_string = strdup("OK");
+               h->response_body_length = actual_length;
+               h->response_body = image;
+               return;
+       }
+
+       do_404(h);
+}
+
+
+// Fetch a user bio (profile)
+void fetch_user_bio(struct http_transaction *h, struct ctdlsession *c, char *username) {
+       do_404(h);      // FIXME finish this
+}
+
+
+// Client requested an object related to a user.
+void object_in_user(struct http_transaction *h, struct ctdlsession *c, char *requested_username) {
+       char object_name[1024];
+
+       extract_token(object_name, h->url, 4, '/', sizeof object_name);
+
+       if (!strcasecmp(object_name, "userpic")) {              // user photo (avatar)
+               fetch_user_photo(h, c, requested_username);
+               return;
+       }
+
+       if (!strcasecmp(object_name, "bio")) {                  // user bio (profile)
+               fetch_user_bio(h, c, requested_username);
+               return;
+       }
+
+       do_404(h);                                              // unknown object
+       return;
+}
+
+
+// Handle REST/DAV requests for the user itself (such as /ctdl/u/username
+// or /ctdl/i/username/ but *not* specific properties of the user)
+void the_user_itself(struct http_transaction *h, struct ctdlsession *c, char *username) {
+       do_404(h);
+}
+
+
+// Dispatcher for "/ctdl/u" and "/ctdl/u/" for the user list
+void user_list(struct http_transaction *h, struct ctdlsession *c) {
+       do_404(h);
+}
+
+
+// Dispatcher for paths starting with /ctdl/u/
+void ctdl_u(struct http_transaction *h, struct ctdlsession *c) {
+       char requested_username[128];
+       char buf[1024];
+
+       // All user-related functions require accessing the user in question.
+       extract_token(requested_username, h->url, 3, '/', sizeof requested_username);
+       unescape_input(requested_username);
+
+       if (IsEmptyStr(requested_username)) {                           //      /ctdl/u/
+               user_list(h, c);
+               return;
+       }
+
+       // At this point we have extracted the name of the user we're interested in.
+       // FIXME should we validate it?
+
+       if (num_tokens(h->url, '/') == 4) {                             //      /ctdl/u/username
+               the_user_itself(h, c, requested_username);
+               return;
+       }
+
+       extract_token(buf, h->url, 4, '/', sizeof buf);
+       if (num_tokens(h->url, '/') == 5) {
+               if (IsEmptyStr(buf)) {
+                       the_user_itself(h, c, requested_username);      //      /ctdl/u/username/ (same as /ctdl/u/username)
+               }
+               else {
+                       object_in_user(h, c, requested_username);       //      /ctdl/u/username/object
+               }
+               return;
+       }
+
+       if (num_tokens(h->url, '/') == 6) {
+               object_in_user(h, c, requested_username);               //      /ctdl/u/username/object/ or possibly /ctdl/u/username/object/component
+               return;
+       }
+
+       // If we get to this point, the client requested an action we don't know how to perform.
+       do_404(h);
+}
diff --git a/webcit-ng/server/util.c b/webcit-ng/server/util.c
new file mode 100644 (file)
index 0000000..72bf660
--- /dev/null
@@ -0,0 +1,97 @@
+//
+// Utility functions
+//
+// Copyright (c) 1996-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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);
+}
diff --git a/webcit-ng/server/webcit.h b/webcit-ng/server/webcit.h
new file mode 100644 (file)
index 0000000..9e6d069
--- /dev/null
@@ -0,0 +1,165 @@
+// webcit.h - "header of headers"
+//
+// Copyright (c) 1996-2022 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.
+
+#define SHOW_ME_VAPPEND_PRINTF
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <syslog.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.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 <sys/time.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 keyval {                                        // key/value pair (for array)
+       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 *url;                              // 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.
+       Array *request_headers;
+       char *request_body;
+       long request_body_length;
+       Array *request_parms;                   // anything after the "?" in the URL
+       int response_code;
+       char *response_string;
+       Array *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 is_room_aide;                       // nonzero if the user has aide rights to THIS room
+       int can_delete_messages;                // nonzeri if the user is permitted to delete messages in THIS room
+       long last_seen;
+       int new_messages;
+       int total_messages;
+       time_t last_access;                     // Timestamp of last request that used this session
+       time_t num_requests_handled;
+       time_t room_mtime;                      // Timestampt of the most recent write activity in this room
+       int new_mail;                           // number of new messages in the user's INBOX
+};
+
+extern char *ssl_cipher_list;
+extern int is_https;                           // nonzero if we are an HTTPS server today
+extern char *ctdl_dir;                         // directory where Citadel Server is running
+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 DEFAULT_SSL_CIPHER_LIST "DEFAULT"       // See http://openssl.org/docs/apps/ciphers.html
+#define WEBSERVER_PORT         80
+#define WEBSERVER_INTERFACE    "*"
+#define CTDL_DIR               "/usr/local/citadel"
+#define DEVELOPER_ID           0
+#define CLIENT_ID              4
+#define TARGET                 "webcit02"      /* Window target for inline URL's */
+
+void worker_entry(int *);
+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_204(struct http_transaction *);
+void do_404(struct http_transaction *);
+void do_412(struct http_transaction *);
+void do_502(struct http_transaction *);
+void output_static(struct http_transaction *);
+int uds_connectsock(char *);
+void ctdl_a(struct http_transaction *, struct ctdlsession *);
+void ctdl_f(struct http_transaction *, struct ctdlsession *);
+void ctdl_r(struct http_transaction *, struct ctdlsession *);
+void ctdl_u(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);
+char *get_url_param(struct http_transaction *h, char *requested_param);
+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);
+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);
+int ctdl_readline(struct ctdlsession *ctdl, char *buf, int maxbytes);
+int ctdl_read_binary(struct ctdlsession *ctdl, char *buf, int bytes_requested);
+void ctdl_c(struct http_transaction *h, struct ctdlsession *c);
+int webserver(char *webserver_interface, int webserver_port, int webserver_protocol);
+void ctdl_printf(struct ctdlsession *ctdl, const char *format,...);
+int webcit_tcp_server(const char *ip_addr, int port_number, int queue_len);
+void UrlizeText(StrBuf* Target, StrBuf *Source, StrBuf *WrkBuf);
+void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum);
diff --git a/webcit-ng/server/webserver.c b/webcit-ng/server/webserver.c
new file mode 100644 (file)
index 0000000..2e22efa
--- /dev/null
@@ -0,0 +1,147 @@
+// 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-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or
+// disclosure are subject to the GNU General Public License v3.
+
+#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.
+               // We don't have to worry about the "thundering herd" problem on modern kernels; for an explanation see
+               // https://stackoverflow.com/questions/2213779/does-the-thundering-herd-problem-exist-on-linux-anymore
+               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);
+}
diff --git a/webcit-ng/static.c b/webcit-ng/static.c
deleted file mode 100644 (file)
index 632773d..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-// Output static content
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#include "webcit.h"
-
-
-// Called from perform_request() to handle static content.
-void output_static(struct http_transaction *h) {
-       char filename[PATH_MAX];
-       struct stat statbuf;
-
-       syslog(LOG_DEBUG, "static: %s", h->url);
-
-       if (!strncasecmp(h->url, "/ctdl/s/", 8)) {
-               snprintf(filename, sizeof filename, "static/%s", &h->url[8]);
-       }
-       else if (!strncasecmp(h->url, "/.well-known/", 13)) {
-               snprintf(filename, sizeof filename, "static/.well-known/%s", &h->url[13]);
-       }
-       else if (!strcasecmp(h->url, "/favicon.ico")) {
-               snprintf(filename, sizeof filename, "static/images/favicon.ico");
-       }
-       else {
-               do_404(h);
-               return;
-       }
-
-       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) {
-               syslog(LOG_DEBUG, "%s: %s", filename, strerror(errno));
-               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);
-       }
-}
diff --git a/webcit-ng/tcp_sockets.c b/webcit-ng/tcp_sockets.c
deleted file mode 100644 (file)
index 5de373a..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-//
-// TCP sockets layer
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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);
-}
diff --git a/webcit-ng/text2html.c b/webcit-ng/text2html.c
deleted file mode 100644 (file)
index 1a1bf14..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-// Convert text/plain to text/html
-//
-// Copyright (c) 2017-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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);
-}
diff --git a/webcit-ng/tls.c b/webcit-ng/tls.c
deleted file mode 100644 (file)
index c0e26f0..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-// Functions in this module handle SSL encryption when WebCit is running
-// as an HTTPS server.
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#include "webcit.h"
-
-SSL_CTX *ssl_ctx;              // global SSL context
-char key_file[PATH_MAX] = "";
-char cert_file[PATH_MAX] = "";
-char *ssl_cipher_list = DEFAULT_SSL_CIPHER_LIST;
-
-
-// Set the private key and certificate chain for the global SSL Context.
-// This is called during initialization, and can be called again later if the certificate changes.
-void bind_to_key_and_certificate(void) {
-       SSL_CTX *old_ctx, *new_ctx;
-
-       if (!(new_ctx = SSL_CTX_new(SSLv23_server_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(new_ctx, ssl_cipher_list))) {
-               syslog(LOG_WARNING, "SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
-               return;
-       }
-
-       if (IsEmptyStr(key_file)) {
-               snprintf(key_file, sizeof key_file, "%s/keys/citadel.key", ctdl_dir);
-       }
-       if (IsEmptyStr(cert_file)) {
-               snprintf(cert_file, sizeof key_file, "%s/keys/citadel.cer", ctdl_dir);
-       }
-
-       syslog(LOG_DEBUG, "crypto: [re]installing key \"%s\" and certificate \"%s\"", key_file, cert_file);
-
-       SSL_CTX_use_certificate_chain_file(new_ctx, cert_file);
-       SSL_CTX_use_PrivateKey_file(new_ctx, key_file, SSL_FILETYPE_PEM);
-
-       if ( !SSL_CTX_check_private_key(new_ctx) ) {
-               syslog(LOG_WARNING, "crypto: cannot install certificate: %s", ERR_reason_error_string(ERR_get_error()));
-       }
-
-       old_ctx = ssl_ctx;
-       ssl_ctx = new_ctx;
-       sleep(1);
-       SSL_CTX_free(old_ctx);
-}
-
-
-// Initialize ssl engine, load certs and initialize openssl internals
-void init_ssl(void) {
-
-       // Initialize the OpenSSL library
-       SSL_load_error_strings();
-       ERR_load_crypto_strings();
-       OpenSSL_add_all_algorithms();
-       SSL_library_init();
-
-       // Now try to bind to the key and certificate.
-       bind_to_key_and_certificate();
-}
-
-
-// Check the modification time of the key and certificate -- reload if they changed
-void update_key_and_cert_if_needed(void) {
-       static time_t previous_mtime = 0;
-       struct stat keystat;
-       struct stat certstat;
-
-       if (stat(key_file, &keystat) != 0) {
-               syslog(LOG_ERR, "%s: %s", key_file, strerror(errno));
-               return;
-       }
-       if (stat(cert_file, &certstat) != 0) {
-               syslog(LOG_ERR, "%s: %s", cert_file, strerror(errno));
-               return;
-       }
-
-       if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) {
-               bind_to_key_and_certificate();
-               previous_mtime = keystat.st_mtime + certstat.st_mtime;
-       }
-}
-
-
-// starts SSL/TLS encryption for the current session.
-void starttls(struct client_handle *ch) {
-       int retval, bits, alg_bits;
-
-       if (!ssl_ctx) {
-               return;
-       }
-
-       // Check the modification time of the key and certificate -- reload if they changed
-       update_key_and_cert_if_needed();
-
-       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) {
-               syslog(LOG_WARNING, "SSL_accept failed: %s", ERR_reason_error_string(ERR_get_error()));
-       }
-       else {
-               syslog(LOG_INFO, "SSL_accept success");
-       }
-       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: %s", ERR_reason_error_string(ERR_get_error()));
-                       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);
-}
diff --git a/webcit-ng/user_functions.c b/webcit-ng/user_functions.c
deleted file mode 100644 (file)
index 0b4548e..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-// User functions
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#include "webcit.h"
-
-
-// Fetch a user photo (avatar)
-void fetch_user_photo(struct http_transaction *h, struct ctdlsession *c, char *username) {
-       char buf[1024];
-       int content_length = 0;
-       char content_type[1024];
-       char *image = NULL;
-       int actual_length = 0;
-
-       ctdl_printf(c, "DLUI %s", username);
-       ctdl_readline(c, buf, sizeof(buf));
-       if (buf[0] == '6') {
-               content_length = extract_int(&buf[4], 0);
-               extract_token(content_type, &buf[4], 3, '|', sizeof content_type);
-
-               image = malloc(content_length);
-               if (image == NULL) {
-                       do_502(h);
-                       return;
-               }
-               actual_length = ctdl_read_binary(c, image, content_length);
-
-               add_response_header(h, strdup("Content-type"), strdup(content_type));
-               h->response_code = 200;
-               h->response_string = strdup("OK");
-               h->response_body_length = actual_length;
-               h->response_body = image;
-               return;
-       }
-
-       do_404(h);
-}
-
-
-// Fetch a user bio (profile)
-void fetch_user_bio(struct http_transaction *h, struct ctdlsession *c, char *username) {
-       do_404(h);      // FIXME finish this
-}
-
-
-// Client requested an object related to a user.
-void object_in_user(struct http_transaction *h, struct ctdlsession *c, char *requested_username) {
-       char object_name[1024];
-
-       extract_token(object_name, h->url, 4, '/', sizeof object_name);
-
-       if (!strcasecmp(object_name, "userpic")) {              // user photo (avatar)
-               fetch_user_photo(h, c, requested_username);
-               return;
-       }
-
-       if (!strcasecmp(object_name, "bio")) {                  // user bio (profile)
-               fetch_user_bio(h, c, requested_username);
-               return;
-       }
-
-       do_404(h);                                              // unknown object
-       return;
-}
-
-
-// Handle REST/DAV requests for the user itself (such as /ctdl/u/username
-// or /ctdl/i/username/ but *not* specific properties of the user)
-void the_user_itself(struct http_transaction *h, struct ctdlsession *c, char *username) {
-       do_404(h);
-}
-
-
-// Dispatcher for "/ctdl/u" and "/ctdl/u/" for the user list
-void user_list(struct http_transaction *h, struct ctdlsession *c) {
-       do_404(h);
-}
-
-
-// Dispatcher for paths starting with /ctdl/u/
-void ctdl_u(struct http_transaction *h, struct ctdlsession *c) {
-       char requested_username[128];
-       char buf[1024];
-
-       // All user-related functions require accessing the user in question.
-       extract_token(requested_username, h->url, 3, '/', sizeof requested_username);
-       unescape_input(requested_username);
-
-       if (IsEmptyStr(requested_username)) {                           //      /ctdl/u/
-               user_list(h, c);
-               return;
-       }
-
-       // At this point we have extracted the name of the user we're interested in.
-       // FIXME should we validate it?
-
-       if (num_tokens(h->url, '/') == 4) {                             //      /ctdl/u/username
-               the_user_itself(h, c, requested_username);
-               return;
-       }
-
-       extract_token(buf, h->url, 4, '/', sizeof buf);
-       if (num_tokens(h->url, '/') == 5) {
-               if (IsEmptyStr(buf)) {
-                       the_user_itself(h, c, requested_username);      //      /ctdl/u/username/ (same as /ctdl/u/username)
-               }
-               else {
-                       object_in_user(h, c, requested_username);       //      /ctdl/u/username/object
-               }
-               return;
-       }
-
-       if (num_tokens(h->url, '/') == 6) {
-               object_in_user(h, c, requested_username);               //      /ctdl/u/username/object/ or possibly /ctdl/u/username/object/component
-               return;
-       }
-
-       // If we get to this point, the client requested an action we don't know how to perform.
-       do_404(h);
-}
diff --git a/webcit-ng/util.c b/webcit-ng/util.c
deleted file mode 100644 (file)
index 72bf660..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-//
-// Utility functions
-//
-// Copyright (c) 1996-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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);
-}
diff --git a/webcit-ng/webcit.h b/webcit-ng/webcit.h
deleted file mode 100644 (file)
index 9e6d069..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-// webcit.h - "header of headers"
-//
-// Copyright (c) 1996-2022 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.
-
-#define SHOW_ME_VAPPEND_PRINTF
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <syslog.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/stat.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 <sys/time.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 keyval {                                        // key/value pair (for array)
-       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 *url;                              // 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.
-       Array *request_headers;
-       char *request_body;
-       long request_body_length;
-       Array *request_parms;                   // anything after the "?" in the URL
-       int response_code;
-       char *response_string;
-       Array *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 is_room_aide;                       // nonzero if the user has aide rights to THIS room
-       int can_delete_messages;                // nonzeri if the user is permitted to delete messages in THIS room
-       long last_seen;
-       int new_messages;
-       int total_messages;
-       time_t last_access;                     // Timestamp of last request that used this session
-       time_t num_requests_handled;
-       time_t room_mtime;                      // Timestampt of the most recent write activity in this room
-       int new_mail;                           // number of new messages in the user's INBOX
-};
-
-extern char *ssl_cipher_list;
-extern int is_https;                           // nonzero if we are an HTTPS server today
-extern char *ctdl_dir;                         // directory where Citadel Server is running
-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 DEFAULT_SSL_CIPHER_LIST "DEFAULT"       // See http://openssl.org/docs/apps/ciphers.html
-#define WEBSERVER_PORT         80
-#define WEBSERVER_INTERFACE    "*"
-#define CTDL_DIR               "/usr/local/citadel"
-#define DEVELOPER_ID           0
-#define CLIENT_ID              4
-#define TARGET                 "webcit02"      /* Window target for inline URL's */
-
-void worker_entry(int *);
-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_204(struct http_transaction *);
-void do_404(struct http_transaction *);
-void do_412(struct http_transaction *);
-void do_502(struct http_transaction *);
-void output_static(struct http_transaction *);
-int uds_connectsock(char *);
-void ctdl_a(struct http_transaction *, struct ctdlsession *);
-void ctdl_f(struct http_transaction *, struct ctdlsession *);
-void ctdl_r(struct http_transaction *, struct ctdlsession *);
-void ctdl_u(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);
-char *get_url_param(struct http_transaction *h, char *requested_param);
-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);
-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);
-int ctdl_readline(struct ctdlsession *ctdl, char *buf, int maxbytes);
-int ctdl_read_binary(struct ctdlsession *ctdl, char *buf, int bytes_requested);
-void ctdl_c(struct http_transaction *h, struct ctdlsession *c);
-int webserver(char *webserver_interface, int webserver_port, int webserver_protocol);
-void ctdl_printf(struct ctdlsession *ctdl, const char *format,...);
-int webcit_tcp_server(const char *ip_addr, int port_number, int queue_len);
-void UrlizeText(StrBuf* Target, StrBuf *Source, StrBuf *WrkBuf);
-void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum);
diff --git a/webcit-ng/webserver.c b/webcit-ng/webserver.c
deleted file mode 100644 (file)
index 2e22efa..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-// 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-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or
-// disclosure are subject to the GNU General Public License v3.
-
-#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.
-               // We don't have to worry about the "thundering herd" problem on modern kernels; for an explanation see
-               // https://stackoverflow.com/questions/2213779/does-the-thundering-herd-problem-exist-on-linux-anymore
-               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);
-}