/*
* Server-side module for Wiki rooms. This handles things like version control.
*
- * Copyright (c) 2009-2011 by the citadel.org team
+ * Copyright (c) 2009-2020 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 as
- * published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
+ * modify it under the terms of the GNU General Public License, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "sysdep.h"
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
+#include <time.h>
#include <sys/wait.h>
#include <string.h>
#include <limits.h>
#include "config.h"
#include "control.h"
#include "user_ops.h"
+#include "room_ops.h"
#include "database.h"
#include "msgbase.h"
#include "euidindex.h"
* Before allowing a wiki page save to execute, we have to perform version control.
* This involves fetching the old version of the page if it exists.
*/
-int wiki_upload_beforesave(struct CtdlMessage *msg) {
+int wiki_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
struct CitContext *CCC = CC;
long old_msgnum = (-1L);
struct CtdlMessage *old_msg = NULL;
struct CtdlMessage *history_msg = NULL;
char diff_old_filename[PATH_MAX];
char diff_new_filename[PATH_MAX];
+ char diff_out_filename[PATH_MAX];
char diff_cmd[PATH_MAX];
FILE *fp;
int rv;
char history_page[1024];
+ long history_page_len;
char boundary[256];
char prefixed_boundary[258];
char buf[1024];
- int nbytes = 0;
char *diffbuf = NULL;
size_t diffbuf_len = 0;
char *ptr = NULL;
+ long newmsgid;
+ StrBuf *msgidbuf;
if (!CCC->logged_in) return(0); /* Only do this if logged in. */
/* Is this a room with a Wiki in it, don't run this hook. */
- if (CCC->room.QRdefaultview != VIEW_WIKI) {
+ if ((CCC->room.QRdefaultview != VIEW_WIKI) &&
+ (CCC->room.QRdefaultview != VIEW_WIKIMD)) {
return(0);
}
if (msg->cm_format_type != 4) return(0);
/* If there's no EUID we can't do this. Reject the post. */
- if (msg->cm_fields['E'] == NULL) return(1);
+ if (CM_IsEmpty(msg, eExclusiveID)) return(1);
+
+ newmsgid = get_new_message_number();
+ msgidbuf = NewStrBuf();
+ StrBufPrintf(msgidbuf, "%08lX-%08lX@%s/%s",
+ (long unsigned int) time(NULL),
+ (long unsigned int) newmsgid,
+ CtdlGetConfigStr("c_fqdn"),
+ msg->cm_fields[eExclusiveID]
+ );
+
+ CM_SetAsFieldSB(msg, emessageId, &msgidbuf);
- snprintf(history_page, sizeof history_page, "%s_HISTORY_", msg->cm_fields['E']);
+ history_page_len = snprintf(history_page, sizeof history_page,
+ "%s_HISTORY_", msg->cm_fields[eExclusiveID]);
/* Make sure we're saving a real wiki page rather than a wiki history page.
* This is important in order to avoid recursing infinitely into this hook.
*/
- if ( (strlen(msg->cm_fields['E']) >= 9)
- && (!strcasecmp(&msg->cm_fields['E'][strlen(msg->cm_fields['E'])-9], "_HISTORY_"))
+ if ( (msg->cm_lengths[eExclusiveID] >= 9)
+ && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_"))
) {
syslog(LOG_DEBUG, "History page not being historied\n");
return(0);
}
/* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
- if (msg->cm_fields['M'] == NULL) return(0);
+ if (CM_IsEmpty(msg, eMesageText)) return(0);
/* Set the message subject identical to the page name */
- if (msg->cm_fields['U'] != NULL) {
- free(msg->cm_fields['U']);
- }
- msg->cm_fields['U'] = strdup(msg->cm_fields['E']);
+ CM_CopyField(msg, eMsgSubject, eExclusiveID);
/* See if we can retrieve the previous version. */
- old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
+ old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
if (old_msgnum > 0L) {
old_msg = CtdlFetchMessage(old_msgnum, 1);
}
old_msg = NULL;
}
- if ((old_msg != NULL) && (old_msg->cm_fields['M'] == NULL)) { /* old version is corrupt? */
- CtdlFreeMessage(old_msg);
+ if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) { /* old version is corrupt? */
+ CM_Free(old_msg);
old_msg = NULL;
}
/* If no changes were made, don't bother saving it again */
- if ((old_msg != NULL) && (!strcmp(msg->cm_fields['M'], old_msg->cm_fields['M']))) {
- CtdlFreeMessage(old_msg);
+ if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) {
+ CM_Free(old_msg);
return(1);
}
*/
CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
+ CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
if (old_msg != NULL) {
fp = fopen(diff_old_filename, "w");
- rv = fwrite(old_msg->cm_fields['M'], strlen(old_msg->cm_fields['M']), 1, fp);
+ rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp);
fclose(fp);
- CtdlFreeMessage(old_msg);
+ CM_Free(old_msg);
}
fp = fopen(diff_new_filename, "w");
- rv = fwrite(msg->cm_fields['M'], strlen(msg->cm_fields['M']), 1, fp);
+ rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
fclose(fp);
- diffbuf_len = 0;
- diffbuf = NULL;
snprintf(diff_cmd, sizeof diff_cmd,
- "diff -u %s %s",
+ DIFF " -u %s %s >%s",
diff_new_filename,
- ((old_msg != NULL) ? diff_old_filename : "/dev/null")
+ ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
+ diff_out_filename
);
- fp = popen(diff_cmd, "r");
+ syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
+ rv = system(diff_cmd);
+ syslog(LOG_DEBUG, "diff cmd returned %d", rv);
+
+ diffbuf_len = 0;
+ diffbuf = NULL;
+ fp = fopen(diff_out_filename, "r");
+ if (fp == NULL) {
+ fp = fopen("/dev/null", "r");
+ }
if (fp != NULL) {
- do {
- diffbuf = realloc(diffbuf, diffbuf_len + 1025);
- nbytes = fread(&diffbuf[diffbuf_len], 1, 1024, fp);
- diffbuf_len += nbytes;
- } while (nbytes == 1024);
- diffbuf[diffbuf_len] = 0;
- if (pclose(fp) != 0) {
- syslog(LOG_ERR, "pclose() returned an error - diff failed\n");
- }
+ fseek(fp, 0L, SEEK_END);
+ diffbuf_len = ftell(fp);
+ fseek(fp, 0L, SEEK_SET);
+ diffbuf = malloc(diffbuf_len + 1);
+ fread(diffbuf, diffbuf_len, 1, fp);
+ diffbuf[diffbuf_len] = '\0';
+ fclose(fp);
}
- syslog(LOG_DEBUG, "diff length is %d bytes\n", diffbuf_len);
+
+ syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
unlink(diff_old_filename);
unlink(diff_new_filename);
+ unlink(diff_out_filename);
/* Determine whether this was a bogus (empty) edit */
if ((diffbuf_len = 0) && (diffbuf != NULL)) {
/* Create a new history message if necessary */
if (history_msg == NULL) {
+ char *buf;
+ long len;
+
history_msg = malloc(sizeof(struct CtdlMessage));
memset(history_msg, 0, sizeof(struct CtdlMessage));
history_msg->cm_magic = CTDLMESSAGE_MAGIC;
history_msg->cm_anon_type = MES_NORMAL;
history_msg->cm_format_type = FMT_RFC822;
- history_msg->cm_fields['A'] = strdup("Citadel");
- history_msg->cm_fields['R'] = strdup(CCC->room.QRname);
- history_msg->cm_fields['E'] = strdup(history_page);
- history_msg->cm_fields['U'] = strdup(history_page);
- history_msg->cm_fields['1'] = strdup("1"); /* suppress full text indexing */
+ CM_SetField(history_msg, eAuthor, HKEY("Citadel"));
+ if (!IsEmptyStr(CCC->room.QRname)){
+ CM_SetField(history_msg, eRecipient, CCC->room.QRname, strlen(CCC->room.QRname));
+ }
+ CM_SetField(history_msg, eExclusiveID, history_page, history_page_len);
+ CM_SetField(history_msg, eMsgSubject, history_page, history_page_len);
+ CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */
snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
- history_msg->cm_fields['M'] = malloc(1024);
- snprintf(history_msg->cm_fields['M'], 1024,
- "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
- "This is a Citadel wiki history encoded as multipart MIME.\n"
- "Each part is comprised of a diff script representing one change set.\n"
- "\n"
- "--%s--\n"
- ,
- boundary, boundary
+ buf = (char*) malloc(1024);
+ len = snprintf(buf, 1024,
+ "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
+ "This is a Citadel wiki history encoded as multipart MIME.\n"
+ "Each part is comprised of a diff script representing one change set.\n"
+ "\n"
+ "--%s--\n",
+ boundary, boundary
);
+ CM_SetAsField(history_msg, eMesageText, &buf, len);
}
/* Update the history message (regardless of whether it's new or existing) */
/* Remove the Message-ID from the old version of the history message. This will cause a brand
* new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
*/
- if (history_msg->cm_fields['I'] != NULL) {
- free(history_msg->cm_fields['I']);
- history_msg->cm_fields['I'] = NULL;
- }
+ CM_FlushField(history_msg, emessageId);
/* Figure out the boundary string. We do this even when we generated the
* boundary string in the above code, just to be safe and consistent.
*/
- strcpy(boundary, "");
+ *boundary = '\0';
- ptr = history_msg->cm_fields['M'];
+ ptr = history_msg->cm_fields[eMesageText];
do {
ptr = memreadline(ptr, buf, sizeof buf);
if (*ptr != 0) {
}
} while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
- /* Now look for the first boundary. That is where we need to insert our fun.
+ /*
+ * Now look for the first boundary. That is where we need to insert our fun.
*/
if (!IsEmptyStr(boundary)) {
- snprintf(prefixed_boundary, sizeof prefixed_boundary, "--%s", boundary);
- history_msg->cm_fields['M'] = realloc(history_msg->cm_fields['M'],
- strlen(history_msg->cm_fields['M']) + strlen(diffbuf) + 1024
- );
- ptr = bmstrcasestr(history_msg->cm_fields['M'], prefixed_boundary);
+ char *MsgText;
+ long MsgTextLen;
+ time_t Now = time(NULL);
+
+ snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary);
+
+ CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen);
+
+ ptr = bmstrcasestr(MsgText, prefixed_boundary);
if (ptr != NULL) {
- char *the_rest_of_it = strdup(ptr);
- char uuid[32];
+ StrBuf *NewMsgText;
+ char uuid[64];
char memo[512];
- char encoded_memo[768];
+ long memolen;
+ char encoded_memo[1024];
+
+ NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024);
+
generate_uuid(uuid);
- snprintf(memo, sizeof memo, "%s|%ld|%s|%s",
- uuid,
- time(NULL),
- CCC->user.fullname,
- config.c_nodename
- /* no longer logging CCC->cs_inet_email */
- );
- CtdlEncodeBase64(encoded_memo, memo, strlen(memo), 0);
- sprintf(ptr, "--%s\n"
- "Content-type: text/plain\n"
- "Content-Disposition: inline; filename=\"%s\"\n"
- "Content-Transfer-Encoding: 8bit\n"
- "\n"
- "%s\n"
- "%s"
- ,
- boundary,
- encoded_memo,
- diffbuf,
- the_rest_of_it
- );
- free(the_rest_of_it);
+ memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s",
+ uuid,
+ Now,
+ CCC->user.fullname,
+ CtdlGetConfigStr("c_nodename"));
+
+ memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0);
+
+ StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0);
+ StrBufAppendBufPlain(NewMsgText, boundary, -1, 0);
+ StrBufAppendBufPlain(
+ NewMsgText,
+ HKEY("\n"
+ "Content-type: text/plain\n"
+ "Content-Disposition: inline; filename=\""), 0);
+
+ StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0);
+
+ StrBufAppendBufPlain(
+ NewMsgText,
+ HKEY("\"\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "\n"), 0);
+
+ StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0);
+ StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0);
+
+ StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0);
+ free(MsgText);
+ CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText);
+ }
+ else
+ {
+ CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen);
}
- history_msg->cm_fields['T'] = realloc(history_msg->cm_fields['T'], 32);
- snprintf(history_msg->cm_fields['T'], 32, "%ld", time(NULL));
+ CM_SetFieldLONG(history_msg, eTimestamp, Now);
- CtdlSubmitMsg(history_msg, NULL, "", 0);
+ CtdlSubmitMsg(history_msg, NULL, "");
}
else {
syslog(LOG_ALERT, "Empty boundary string in history message. No history!\n");
}
free(diffbuf);
- free(history_msg);
+ CM_Free(history_msg);
return(0);
}
msg = NULL;
}
- if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
- CtdlFreeMessage(msg);
+ if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+ CM_Free(msg);
msg = NULL;
}
cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
- mime_parser(msg->cm_fields['M'], NULL, *wiki_history_callback, NULL, NULL, NULL, 0);
+ mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0);
cprintf("000\n");
- CtdlFreeMessage(msg);
+ CM_Free(msg);
return;
}
CtdlDecodeBase64(memo, filename, strlen(filename));
extract_token(this_rev, memo, 0, '|', sizeof this_rev);
- syslog(LOG_DEBUG, "callback found rev: %s\n", this_rev);
+ striplt(this_rev);
/* Perform the patch */
- fp = popen("patch -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
+ fp = popen(PATCH " -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
if (fp) {
/* Replace the filenames in the patch with the tempfilename we're actually tweaking */
fprintf(fp, "--- %s\n", hecbd->tempfilename);
*/
void wiki_rev(char *pagename, char *rev, char *operation)
{
+ struct CitContext *CCC = CC;
int r;
char history_page_name[270];
long msgnum;
char temp[PATH_MAX];
- char timestamp[64];
struct CtdlMessage *msg;
FILE *fp;
struct HistoryEraserCallBackData hecbd;
/* Begin by fetching the current version of the page. We're going to patch
* backwards through the diffs until we get the one we want.
*/
- msgnum = CtdlLocateMessageByEuid(pagename, &CC->room);
+ msgnum = CtdlLocateMessageByEuid(pagename, &CCC->room);
if (msgnum > 0L) {
msg = CtdlFetchMessage(msgnum, 1);
}
msg = NULL;
}
- if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
- CtdlFreeMessage(msg);
+ if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+ CM_Free(msg);
msg = NULL;
}
CtdlMakeTempFileName(temp, sizeof temp);
fp = fopen(temp, "w");
if (fp != NULL) {
- r = fwrite(msg->cm_fields['M'], strlen(msg->cm_fields['M']), 1, fp);
+ r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
fclose(fp);
}
else {
- syslog(LOG_ALERT, "Cannot open %s: %s\n", temp, strerror(errno));
+ syslog(LOG_ERR, "%s: %m", temp);
}
- CtdlFreeMessage(msg);
+ CM_Free(msg);
/* Get the revision history */
snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
- msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
+ msgnum = CtdlLocateMessageByEuid(history_page_name, &CCC->room);
if (msgnum > 0L) {
msg = CtdlFetchMessage(msgnum, 1);
}
msg = NULL;
}
- if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
- CtdlFreeMessage(msg);
+ if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+ CM_Free(msg);
msg = NULL;
}
memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
hecbd.tempfilename = temp;
hecbd.stop_when = rev;
+ striplt(hecbd.stop_when);
- mime_parser(msg->cm_fields['M'], NULL, *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
- CtdlFreeMessage(msg);
+ mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
+ CM_Free(msg);
/* Were we successful? */
if (hecbd.done == 0) {
msg->cm_format_type = FMT_RFC822;
fp = fopen(temp, "r");
if (fp) {
+ char *msgbuf;
fseek(fp, 0L, SEEK_END);
len = ftell(fp);
fseek(fp, 0L, SEEK_SET);
- msg->cm_fields['M'] = malloc(len + 1);
- rv = fread(msg->cm_fields['M'], len, 1, fp);
+ msgbuf = malloc(len + 1);
+ rv = fread(msgbuf, len, 1, fp);
syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
- msg->cm_fields['M'][len] = 0;
+ msgbuf[len] = '\0';
+ CM_SetAsField(msg, eMesageText, &msgbuf, len);
fclose(fp);
}
if (len <= 0) {
msgnum = (-1L);
}
else if (!strcasecmp(operation, "fetch")) {
- msg->cm_fields['A'] = strdup("Citadel");
+ CM_SetField(msg, eAuthor, HKEY("Citadel"));
CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS); /* Not an error if already exists */
- msgnum = CtdlSubmitMsg(msg, NULL, wwm, 0); /* Store the revision here */
+ msgnum = CtdlSubmitMsg(msg, NULL, wwm); /* Store the revision here */
+
+ /*
+ * WARNING: VILE SLEAZY HACK
+ * This will avoid the 'message xxx is not in this room' security error,
+ * but only if the client fetches the message we just generated immediately
+ * without first trying to perform other fetch operations.
+ */
+ if (CCC->cached_msglist != NULL) {
+ free(CCC->cached_msglist);
+ CCC->cached_msglist = NULL;
+ CCC->cached_num_msgs = 0;
+ }
+ CCC->cached_msglist = malloc(sizeof(long));
+ if (CCC->cached_msglist != NULL) {
+ CCC->cached_num_msgs = 1;
+ CCC->cached_msglist[0] = msgnum;
+ }
+
}
else if (!strcasecmp(operation, "revert")) {
- snprintf(timestamp, sizeof timestamp, "%ld", time(NULL));
- msg->cm_fields['T'] = strdup(timestamp);
- msg->cm_fields['A'] = strdup(CC->user.fullname);
- msg->cm_fields['F'] = strdup(CC->cs_inet_email);
- msg->cm_fields['O'] = strdup(CC->room.QRname);
- msg->cm_fields['N'] = strdup(NODENAME);
- msg->cm_fields['E'] = strdup(pagename);
- msgnum = CtdlSubmitMsg(msg, NULL, "", 0); /* Replace the current revision */
+ CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+ if (!IsEmptyStr(CCC->user.fullname)) {
+ CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
+ }
+
+ if (!IsEmptyStr(CCC->cs_inet_email)) {
+ CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email));
+ }
+
+ if (!IsEmptyStr(CCC->room.QRname)) {
+ CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
+ }
+
+ if (!IsEmptyStr(pagename)) {
+ CM_SetField(msg, eExclusiveID, pagename, strlen(pagename));
+ }
+ msgnum = CtdlSubmitMsg(msg, NULL, ""); /* Replace the current revision */
}
else {
/* Theoretically it is impossible to get here, but throw an error anyway */
msgnum = (-1L);
}
- CtdlFreeMessage(msg);
+ CM_Free(msg);
if (msgnum >= 0L) {
cprintf("%d %ld\n", CIT_OK, msgnum); /* Give the client a msgnum */
}
CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
}
- /* return our Subversion id for the Log */
+ /* return our module name for the log */
return "wiki";
}