"Fossies" - the Fresh Open Source Software Archive

Member "citadel/modules/wiki/serv_wiki.c" (5 Jun 2021, 20630 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "serv_wiki.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

    1 /*
    2  * Server-side module for Wiki rooms.  This handles things like version control. 
    3  * 
    4  * Copyright (c) 2009-2020 by the citadel.org team
    5  *
    6  * This program is open source software.  You can redistribute it and/or
    7  * modify it under the terms of the GNU General Public License, version 3.
    8  *
    9  * This program is distributed in the hope that it will be useful,
   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12  * GNU General Public License for more details.
   13  */
   14 
   15 #include "sysdep.h"
   16 #include <stdlib.h>
   17 #include <unistd.h>
   18 #include <stdio.h>
   19 #include <fcntl.h>
   20 #include <signal.h>
   21 #include <pwd.h>
   22 #include <errno.h>
   23 #include <ctype.h>
   24 #include <sys/types.h>
   25 #include <time.h>
   26 #include <sys/wait.h>
   27 #include <string.h>
   28 #include <limits.h>
   29 #include <libcitadel.h>
   30 #include "citadel.h"
   31 #include "server.h"
   32 #include "citserver.h"
   33 #include "support.h"
   34 #include "config.h"
   35 #include "control.h"
   36 #include "user_ops.h"
   37 #include "room_ops.h"
   38 #include "database.h"
   39 #include "msgbase.h"
   40 #include "euidindex.h"
   41 #include "ctdl_module.h"
   42 
   43 /*
   44  * Data passed back and forth between wiki_rev() and its MIME parser callback
   45  */
   46 struct HistoryEraserCallBackData {
   47     char *tempfilename;     /* name of temp file being patched */
   48     char *stop_when;        /* stop when we hit this uuid */
   49     int done;           /* set to nonzero when we're done patching */
   50 };
   51 
   52 /*
   53  * Name of the temporary room we create to store old revisions when someone requests them.
   54  * We put it in an invalid namespace so the DAP cleans up after us later.
   55  */
   56 char *wwm = "9999999999.WikiWaybackMachine";
   57 
   58 /*
   59  * Before allowing a wiki page save to execute, we have to perform version control.
   60  * This involves fetching the old version of the page if it exists.
   61  */
   62 int wiki_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
   63     struct CitContext *CCC = CC;
   64     long old_msgnum = (-1L);
   65     struct CtdlMessage *old_msg = NULL;
   66     long history_msgnum = (-1L);
   67     struct CtdlMessage *history_msg = NULL;
   68     char diff_old_filename[PATH_MAX];
   69     char diff_new_filename[PATH_MAX];
   70     char diff_out_filename[PATH_MAX];
   71     char diff_cmd[PATH_MAX];
   72     FILE *fp;
   73     int rv;
   74     char history_page[1024];
   75     long history_page_len;
   76     char boundary[256];
   77     char prefixed_boundary[258];
   78     char buf[1024];
   79     char *diffbuf = NULL;
   80     size_t diffbuf_len = 0;
   81     char *ptr = NULL;
   82     long newmsgid;
   83     StrBuf *msgidbuf;
   84 
   85     if (!CCC->logged_in) return(0); /* Only do this if logged in. */
   86 
   87     /* Is this a room with a Wiki in it, don't run this hook. */
   88     if ((CCC->room.QRdefaultview != VIEW_WIKI) &&
   89         (CCC->room.QRdefaultview != VIEW_WIKIMD)) {
   90         return(0);
   91     }
   92 
   93     /* If this isn't a MIME message, don't bother. */
   94     if (msg->cm_format_type != 4) return(0);
   95 
   96     /* If there's no EUID we can't do this.  Reject the post. */
   97     if (CM_IsEmpty(msg, eExclusiveID)) return(1);
   98 
   99     newmsgid = get_new_message_number();
  100     msgidbuf = NewStrBuf();
  101     StrBufPrintf(msgidbuf, "%08lX-%08lX@%s/%s",
  102              (long unsigned int) time(NULL),
  103              (long unsigned int) newmsgid,
  104              CtdlGetConfigStr("c_fqdn"),
  105              msg->cm_fields[eExclusiveID]
  106         );
  107 
  108     CM_SetAsFieldSB(msg, emessageId, &msgidbuf);
  109 
  110     history_page_len = snprintf(history_page, sizeof history_page,
  111                     "%s_HISTORY_", msg->cm_fields[eExclusiveID]);
  112 
  113     /* Make sure we're saving a real wiki page rather than a wiki history page.
  114      * This is important in order to avoid recursing infinitely into this hook.
  115      */
  116     if (    (msg->cm_lengths[eExclusiveID] >= 9)
  117         && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_"))
  118     ) {
  119         syslog(LOG_DEBUG, "History page not being historied\n");
  120         return(0);
  121     }
  122 
  123     /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
  124     if (CM_IsEmpty(msg, eMesageText)) return(0);
  125 
  126     /* Set the message subject identical to the page name */
  127     CM_CopyField(msg, eMsgSubject, eExclusiveID);
  128 
  129     /* See if we can retrieve the previous version. */
  130     old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
  131     if (old_msgnum > 0L) {
  132         old_msg = CtdlFetchMessage(old_msgnum, 1);
  133     }
  134     else {
  135         old_msg = NULL;
  136     }
  137 
  138     if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) {  /* old version is corrupt? */
  139         CM_Free(old_msg);
  140         old_msg = NULL;
  141     }
  142     
  143     /* If no changes were made, don't bother saving it again */
  144     if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) {
  145         CM_Free(old_msg);
  146         return(1);
  147     }
  148 
  149     /*
  150      * Generate diffs
  151      */
  152     CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
  153     CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
  154     CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
  155 
  156     if (old_msg != NULL) {
  157         fp = fopen(diff_old_filename, "w");
  158         rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp);
  159         fclose(fp);
  160         CM_Free(old_msg);
  161     }
  162 
  163     fp = fopen(diff_new_filename, "w");
  164     rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
  165     fclose(fp);
  166 
  167     snprintf(diff_cmd, sizeof diff_cmd,
  168         DIFF " -u %s %s >%s",
  169         diff_new_filename,
  170         ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
  171         diff_out_filename
  172     );
  173     syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
  174     rv = system(diff_cmd);
  175     syslog(LOG_DEBUG, "diff cmd returned %d", rv);
  176 
  177     diffbuf_len = 0;
  178     diffbuf = NULL;
  179     fp = fopen(diff_out_filename, "r");
  180     if (fp == NULL) {
  181         fp = fopen("/dev/null", "r");
  182     }
  183     if (fp != NULL) {
  184         fseek(fp, 0L, SEEK_END);
  185         diffbuf_len = ftell(fp);
  186         fseek(fp, 0L, SEEK_SET);
  187         diffbuf = malloc(diffbuf_len + 1);
  188         fread(diffbuf, diffbuf_len, 1, fp);
  189         diffbuf[diffbuf_len] = '\0';
  190         fclose(fp);
  191     }
  192 
  193     syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
  194 
  195     unlink(diff_old_filename);
  196     unlink(diff_new_filename);
  197     unlink(diff_out_filename);
  198 
  199     /* Determine whether this was a bogus (empty) edit */
  200     if ((diffbuf_len = 0) && (diffbuf != NULL)) {
  201         free(diffbuf);
  202         diffbuf = NULL;
  203     }
  204     if (diffbuf == NULL) {
  205         return(1);      /* No changes at all?  Abandon the post entirely! */
  206     }
  207 
  208     /* Now look for the existing edit history */
  209 
  210     history_msgnum = CtdlLocateMessageByEuid(history_page, &CCC->room);
  211     history_msg = NULL;
  212     if (history_msgnum > 0L) {
  213         history_msg = CtdlFetchMessage(history_msgnum, 1);
  214     }
  215 
  216     /* Create a new history message if necessary */
  217     if (history_msg == NULL) {
  218         char *buf;
  219         long len;
  220 
  221         history_msg = malloc(sizeof(struct CtdlMessage));
  222         memset(history_msg, 0, sizeof(struct CtdlMessage));
  223         history_msg->cm_magic = CTDLMESSAGE_MAGIC;
  224         history_msg->cm_anon_type = MES_NORMAL;
  225         history_msg->cm_format_type = FMT_RFC822;
  226         CM_SetField(history_msg, eAuthor, HKEY("Citadel"));
  227         if (!IsEmptyStr(CCC->room.QRname)){
  228             CM_SetField(history_msg, eRecipient, CCC->room.QRname, strlen(CCC->room.QRname));
  229         }
  230         CM_SetField(history_msg, eExclusiveID, history_page, history_page_len);
  231         CM_SetField(history_msg, eMsgSubject, history_page, history_page_len);
  232         CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */
  233         snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
  234         buf = (char*) malloc(1024);
  235         len = snprintf(buf, 1024,
  236                    "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
  237                    "This is a Citadel wiki history encoded as multipart MIME.\n"
  238                    "Each part is comprised of a diff script representing one change set.\n"
  239                    "\n"
  240                    "--%s--\n",
  241                    boundary, boundary
  242         );
  243         CM_SetAsField(history_msg, eMesageText, &buf, len);
  244     }
  245 
  246     /* Update the history message (regardless of whether it's new or existing) */
  247 
  248     /* Remove the Message-ID from the old version of the history message.  This will cause a brand
  249      * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
  250      */
  251     CM_FlushField(history_msg, emessageId);
  252 
  253     /* Figure out the boundary string.  We do this even when we generated the
  254      * boundary string in the above code, just to be safe and consistent.
  255      */
  256     *boundary = '\0';
  257 
  258     ptr = history_msg->cm_fields[eMesageText];
  259     do {
  260         ptr = memreadline(ptr, buf, sizeof buf);
  261         if (*ptr != 0) {
  262             striplt(buf);
  263             if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
  264                 if (
  265                     (bmstrcasestr(buf, "multipart") != NULL)
  266                     && (bmstrcasestr(buf, "boundary=") != NULL)
  267                 ) {
  268                     safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
  269                     char *qu;
  270                     qu = strchr(boundary, '\"');
  271                     if (qu) {
  272                         strcpy(boundary, ++qu);
  273                     }
  274                     qu = strchr(boundary, '\"');
  275                     if (qu) {
  276                         *qu = 0;
  277                     }
  278                 }
  279             }
  280         }
  281     } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
  282 
  283     /*
  284      * Now look for the first boundary.  That is where we need to insert our fun.
  285      */
  286     if (!IsEmptyStr(boundary)) {
  287         char *MsgText;
  288         long MsgTextLen;
  289         time_t Now = time(NULL);
  290 
  291         snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary);
  292         
  293         CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen);
  294 
  295         ptr = bmstrcasestr(MsgText, prefixed_boundary);
  296         if (ptr != NULL) {
  297             StrBuf *NewMsgText;
  298             char uuid[64];
  299             char memo[512];
  300             long memolen;
  301             char encoded_memo[1024];
  302             
  303             NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024);
  304 
  305             generate_uuid(uuid);
  306             memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s", 
  307                        uuid,
  308                        Now,
  309                        CCC->user.fullname,
  310                        CtdlGetConfigStr("c_nodename"));
  311 
  312             memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0);
  313 
  314             StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0);
  315             StrBufAppendBufPlain(NewMsgText, boundary, -1, 0);
  316             StrBufAppendBufPlain(
  317                 NewMsgText, 
  318                 HKEY("\n"
  319                      "Content-type: text/plain\n"
  320                      "Content-Disposition: inline; filename=\""), 0);
  321 
  322             StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0);
  323 
  324             StrBufAppendBufPlain(
  325                 NewMsgText, 
  326                 HKEY("\"\n"
  327                      "Content-Transfer-Encoding: 8bit\n"
  328                      "\n"), 0);
  329 
  330             StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0);
  331             StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0);
  332 
  333             StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0);
  334             free(MsgText);
  335             CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText); 
  336         }
  337         else
  338         {
  339             CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen); 
  340         }
  341 
  342         CM_SetFieldLONG(history_msg, eTimestamp, Now);
  343     
  344         CtdlSubmitMsg(history_msg, NULL, "");
  345     }
  346     else {
  347         syslog(LOG_ALERT, "Empty boundary string in history message.  No history!\n");
  348     }
  349 
  350     free(diffbuf);
  351     CM_Free(history_msg);
  352     return(0);
  353 }
  354 
  355 
  356 /*
  357  * MIME Parser callback for wiki_history()
  358  *
  359  * The "filename" field will contain a memo field.  All we have to do is decode
  360  * the base64 and output it.  The data is already in a delimited format suitable
  361  * for our client protocol.
  362  */
  363 void wiki_history_callback(char *name, char *filename, char *partnum, char *disp,
  364            void *content, char *cbtype, char *cbcharset, size_t length,
  365            char *encoding, char *cbid, void *cbuserdata)
  366 {
  367     char memo[1024];
  368 
  369     CtdlDecodeBase64(memo, filename, strlen(filename));
  370     cprintf("%s\n", memo);
  371 }
  372 
  373 
  374 /*
  375  * Fetch a list of revisions for a particular wiki page
  376  */
  377 void wiki_history(char *pagename) {
  378     int r;
  379     char history_page_name[270];
  380     long msgnum;
  381     struct CtdlMessage *msg;
  382 
  383     r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
  384     if (r != om_ok) {
  385         if (r == om_not_logged_in) {
  386             cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
  387         }
  388         else {
  389             cprintf("%d An unknown error has occurred.\n", ERROR);
  390         }
  391         return;
  392     }
  393 
  394     snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
  395     msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
  396     if (msgnum > 0L) {
  397         msg = CtdlFetchMessage(msgnum, 1);
  398     }
  399     else {
  400         msg = NULL;
  401     }
  402 
  403     if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
  404         CM_Free(msg);
  405         msg = NULL;
  406     }
  407 
  408     if (msg == NULL) {
  409         cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
  410         return;
  411     }
  412 
  413     
  414     cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
  415     mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0);
  416     cprintf("000\n");
  417 
  418     CM_Free(msg);
  419     return;
  420 }
  421 
  422 /*
  423  * MIME Parser callback for wiki_rev()
  424  *
  425  * The "filename" field will contain a memo field, which includes (among other things)
  426  * the uuid of this revision.  After we hit the desired revision, we stop processing.
  427  *
  428  * The "content" filed will contain "diff" output suitable for applying via "patch"
  429  * to our temporary file.
  430  */
  431 void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp,
  432            void *content, char *cbtype, char *cbcharset, size_t length,
  433            char *encoding, char *cbid, void *cbuserdata)
  434 {
  435     struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
  436     char memo[1024];
  437     char this_rev[256];
  438     FILE *fp;
  439     char *ptr = NULL;
  440     char buf[1024];
  441 
  442     /* Did a previous callback already indicate that we've reached our target uuid?
  443      * If so, don't process anything else.
  444      */
  445     if (hecbd->done) {
  446         return;
  447     }
  448 
  449     CtdlDecodeBase64(memo, filename, strlen(filename));
  450     extract_token(this_rev, memo, 0, '|', sizeof this_rev);
  451     striplt(this_rev);
  452 
  453     /* Perform the patch */
  454     fp = popen(PATCH " -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
  455     if (fp) {
  456         /* Replace the filenames in the patch with the tempfilename we're actually tweaking */
  457         fprintf(fp, "--- %s\n", hecbd->tempfilename);
  458         fprintf(fp, "+++ %s\n", hecbd->tempfilename);
  459 
  460         ptr = (char *)content;
  461         int linenum = 0;
  462         do {
  463             ++linenum;
  464             ptr = memreadline(ptr, buf, sizeof buf);
  465             if (*ptr != 0) {
  466                 if (linenum <= 2) {
  467                     /* skip the first two lines; they contain bogus filenames */
  468                 }
  469                 else {
  470                     fprintf(fp, "%s\n", buf);
  471                 }
  472             }
  473         } while ((*ptr != 0) && (ptr < ((char*)content + length)));
  474         if (pclose(fp) != 0) {
  475             syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
  476         }
  477     }
  478 
  479     if (!strcasecmp(this_rev, hecbd->stop_when)) {
  480         /* Found our target rev.  Tell any subsequent callbacks to suppress processing. */
  481         syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
  482         hecbd->done = 1;
  483     }
  484 }
  485 
  486 
  487 /*
  488  * Fetch a specific revision of a wiki page.  The "operation" string may be set to "fetch" in order
  489  * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert"
  490  * to revert the currently active page to that revision.
  491  */
  492 void wiki_rev(char *pagename, char *rev, char *operation)
  493 {
  494     struct CitContext *CCC = CC;
  495     int r;
  496     char history_page_name[270];
  497     long msgnum;
  498     char temp[PATH_MAX];
  499     struct CtdlMessage *msg;
  500     FILE *fp;
  501     struct HistoryEraserCallBackData hecbd;
  502     long len = 0L;
  503     int rv;
  504 
  505     r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
  506     if (r != om_ok) {
  507         if (r == om_not_logged_in) {
  508             cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
  509         }
  510         else {
  511             cprintf("%d An unknown error has occurred.\n", ERROR);
  512         }
  513         return;
  514     }
  515 
  516     if (!strcasecmp(operation, "revert")) {
  517         r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, NULL, POST_LOGGED_IN, 0);
  518         if (r != 0) {
  519             cprintf("%d %s\n", r, temp);
  520             return;
  521         }
  522     }
  523 
  524     /* Begin by fetching the current version of the page.  We're going to patch
  525      * backwards through the diffs until we get the one we want.
  526      */
  527     msgnum = CtdlLocateMessageByEuid(pagename, &CCC->room);
  528     if (msgnum > 0L) {
  529         msg = CtdlFetchMessage(msgnum, 1);
  530     }
  531     else {
  532         msg = NULL;
  533     }
  534 
  535     if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
  536         CM_Free(msg);
  537         msg = NULL;
  538     }
  539 
  540     if (msg == NULL) {
  541         cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
  542         return;
  543     }
  544 
  545     /* Output it to a temporary file */
  546 
  547     CtdlMakeTempFileName(temp, sizeof temp);
  548     fp = fopen(temp, "w");
  549     if (fp != NULL) {
  550         r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
  551         fclose(fp);
  552     }
  553     else {
  554         syslog(LOG_ERR, "%s: %m", temp);
  555     }
  556     CM_Free(msg);
  557 
  558     /* Get the revision history */
  559 
  560     snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
  561     msgnum = CtdlLocateMessageByEuid(history_page_name, &CCC->room);
  562     if (msgnum > 0L) {
  563         msg = CtdlFetchMessage(msgnum, 1);
  564     }
  565     else {
  566         msg = NULL;
  567     }
  568 
  569     if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
  570         CM_Free(msg);
  571         msg = NULL;
  572     }
  573 
  574     if (msg == NULL) {
  575         cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
  576         return;
  577     }
  578 
  579     /* Start patching backwards (newest to oldest) through the revision history until we
  580      * hit the revision uuid requested by the user.  (The callback will perform each one.)
  581      */
  582 
  583     memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
  584     hecbd.tempfilename = temp;
  585     hecbd.stop_when = rev;
  586     striplt(hecbd.stop_when);
  587 
  588     mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
  589     CM_Free(msg);
  590 
  591     /* Were we successful? */
  592     if (hecbd.done == 0) {
  593         cprintf("%d Revision '%s' of page '%s' was not found.\n",
  594             ERROR + MESSAGE_NOT_FOUND, rev, pagename
  595         );
  596     }
  597 
  598     /* We have the desired revision on disk.  Now do something with it. */
  599 
  600     else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
  601         msg = malloc(sizeof(struct CtdlMessage));
  602         memset(msg, 0, sizeof(struct CtdlMessage));
  603         msg->cm_magic = CTDLMESSAGE_MAGIC;
  604         msg->cm_anon_type = MES_NORMAL;
  605         msg->cm_format_type = FMT_RFC822;
  606         fp = fopen(temp, "r");
  607         if (fp) {
  608             char *msgbuf;
  609             fseek(fp, 0L, SEEK_END);
  610             len = ftell(fp);
  611             fseek(fp, 0L, SEEK_SET);
  612             msgbuf = malloc(len + 1);
  613             rv = fread(msgbuf, len, 1, fp);
  614             syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
  615             msgbuf[len] = '\0';
  616             CM_SetAsField(msg, eMesageText, &msgbuf, len);
  617             fclose(fp);
  618         }
  619         if (len <= 0) {
  620             msgnum = (-1L);
  621         }
  622         else if (!strcasecmp(operation, "fetch")) {
  623             CM_SetField(msg, eAuthor, HKEY("Citadel"));
  624             CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS);  /* Not an error if already exists */
  625             msgnum = CtdlSubmitMsg(msg, NULL, wwm);     /* Store the revision here */
  626 
  627             /*
  628              * WARNING: VILE SLEAZY HACK
  629              * This will avoid the 'message xxx is not in this room' security error,
  630              * but only if the client fetches the message we just generated immediately
  631              * without first trying to perform other fetch operations.
  632              */
  633             if (CCC->cached_msglist != NULL) {
  634                 free(CCC->cached_msglist);
  635                 CCC->cached_msglist = NULL;
  636                 CCC->cached_num_msgs = 0;
  637             }
  638             CCC->cached_msglist = malloc(sizeof(long));
  639             if (CCC->cached_msglist != NULL) {
  640                 CCC->cached_num_msgs = 1;
  641                 CCC->cached_msglist[0] = msgnum;
  642             }
  643 
  644         }
  645         else if (!strcasecmp(operation, "revert")) {
  646             CM_SetFieldLONG(msg, eTimestamp, time(NULL));
  647             if (!IsEmptyStr(CCC->user.fullname)) {
  648                 CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
  649             }
  650 
  651             if (!IsEmptyStr(CCC->cs_inet_email)) {
  652                 CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email));
  653             }
  654 
  655             if (!IsEmptyStr(CCC->room.QRname)) {
  656                 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
  657             }
  658 
  659             if (!IsEmptyStr(pagename)) {
  660                 CM_SetField(msg, eExclusiveID, pagename, strlen(pagename));
  661             }
  662             msgnum = CtdlSubmitMsg(msg, NULL, "");      /* Replace the current revision */
  663         }
  664         else {
  665             /* Theoretically it is impossible to get here, but throw an error anyway */
  666             msgnum = (-1L);
  667         }
  668         CM_Free(msg);
  669         if (msgnum >= 0L) {
  670             cprintf("%d %ld\n", CIT_OK, msgnum);        /* Give the client a msgnum */
  671         }
  672         else {
  673             cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
  674         }
  675     }
  676 
  677     /* We did all this work for nothing.  Express anguish to the caller. */
  678     else {
  679         cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
  680     }
  681 
  682     unlink(temp);
  683     return;
  684 }
  685 
  686 
  687 
  688 /*
  689  * commands related to wiki management
  690  */
  691 void cmd_wiki(char *argbuf) {
  692     char subcmd[32];
  693     char pagename[256];
  694     char rev[128];
  695     char operation[16];
  696 
  697     extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
  698 
  699     if (!strcasecmp(subcmd, "history")) {
  700         extract_token(pagename, argbuf, 1, '|', sizeof pagename);
  701         wiki_history(pagename);
  702         return;
  703     }
  704 
  705     if (!strcasecmp(subcmd, "rev")) {
  706         extract_token(pagename, argbuf, 1, '|', sizeof pagename);
  707         extract_token(rev, argbuf, 2, '|', sizeof rev);
  708         extract_token(operation, argbuf, 3, '|', sizeof operation);
  709         wiki_rev(pagename, rev, operation);
  710         return;
  711     }
  712 
  713     cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
  714 }
  715 
  716 
  717 
  718 /*
  719  * Module initialization
  720  */
  721 CTDL_MODULE_INIT(wiki)
  722 {
  723     if (!threading)
  724     {
  725         CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
  726         CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
  727     }
  728 
  729     /* return our module name for the log */
  730     return "wiki";
  731 }