Back to index

citadel  8.12
serv_wiki.c
Go to the documentation of this file.
00001 /*
00002  * Server-side module for Wiki rooms.  This handles things like version control. 
00003  * 
00004  * Copyright (c) 2009-2012 by the citadel.org team
00005  *
00006  * This program is open source software.  You can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License, version 3.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  */
00014 
00015 #include "sysdep.h"
00016 #include <stdlib.h>
00017 #include <unistd.h>
00018 #include <stdio.h>
00019 #include <fcntl.h>
00020 #include <signal.h>
00021 #include <pwd.h>
00022 #include <errno.h>
00023 #include <ctype.h>
00024 #include <sys/types.h>
00025 
00026 #if TIME_WITH_SYS_TIME
00027 # include <sys/time.h>
00028 # include <time.h>
00029 #else
00030 # if HAVE_SYS_TIME_H
00031 #  include <sys/time.h>
00032 # else
00033 #  include <time.h>
00034 # endif
00035 #endif
00036 
00037 #include <sys/wait.h>
00038 #include <string.h>
00039 #include <limits.h>
00040 #include <libcitadel.h>
00041 #include "citadel.h"
00042 #include "server.h"
00043 #include "citserver.h"
00044 #include "support.h"
00045 #include "config.h"
00046 #include "control.h"
00047 #include "user_ops.h"
00048 #include "database.h"
00049 #include "msgbase.h"
00050 #include "euidindex.h"
00051 #include "ctdl_module.h"
00052 
00053 /*
00054  * Data passed back and forth between wiki_rev() and its MIME parser callback
00055  */
00056 struct HistoryEraserCallBackData {
00057        char *tempfilename;         /* name of temp file being patched */
00058        char *stop_when;            /* stop when we hit this uuid */
00059        int done;                   /* set to nonzero when we're done patching */
00060 };
00061 
00062 /*
00063  * Name of the temporary room we create to store old revisions when someone requests them.
00064  * We put it in an invalid namespace so the DAP cleans up after us later.
00065  */
00066 char *wwm = "9999999999.WikiWaybackMachine";
00067 
00068 /*
00069  * Before allowing a wiki page save to execute, we have to perform version control.
00070  * This involves fetching the old version of the page if it exists.
00071  */
00072 int wiki_upload_beforesave(struct CtdlMessage *msg) {
00073        struct CitContext *CCC = CC;
00074        long old_msgnum = (-1L);
00075        struct CtdlMessage *old_msg = NULL;
00076        long history_msgnum = (-1L);
00077        struct CtdlMessage *history_msg = NULL;
00078        char diff_old_filename[PATH_MAX];
00079        char diff_new_filename[PATH_MAX];
00080        char diff_out_filename[PATH_MAX];
00081        char diff_cmd[PATH_MAX];
00082        FILE *fp;
00083        int rv;
00084        char history_page[1024];
00085        char boundary[256];
00086        char prefixed_boundary[258];
00087        char buf[1024];
00088        char *diffbuf = NULL;
00089        size_t diffbuf_len = 0;
00090        char *ptr = NULL;
00091 
00092        if (!CCC->logged_in) return(0);    /* Only do this if logged in. */
00093 
00094        /* Is this a room with a Wiki in it, don't run this hook. */
00095        if (CCC->room.QRdefaultview != VIEW_WIKI) {
00096               return(0);
00097        }
00098 
00099        /* If this isn't a MIME message, don't bother. */
00100        if (msg->cm_format_type != 4) return(0);
00101 
00102        /* If there's no EUID we can't do this.  Reject the post. */
00103        if (msg->cm_fields['E'] == NULL) return(1);
00104 
00105        snprintf(history_page, sizeof history_page, "%s_HISTORY_", msg->cm_fields['E']);
00106 
00107        /* Make sure we're saving a real wiki page rather than a wiki history page.
00108         * This is important in order to avoid recursing infinitely into this hook.
00109         */
00110        if (   (strlen(msg->cm_fields['E']) >= 9)
00111               && (!strcasecmp(&msg->cm_fields['E'][strlen(msg->cm_fields['E'])-9], "_HISTORY_"))
00112        ) {
00113               syslog(LOG_DEBUG, "History page not being historied\n");
00114               return(0);
00115        }
00116 
00117        /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
00118        if (msg->cm_fields['M'] == NULL) return(0);
00119 
00120        /* Set the message subject identical to the page name */
00121        if (msg->cm_fields['U'] != NULL) {
00122               free(msg->cm_fields['U']);
00123        }
00124        msg->cm_fields['U'] = strdup(msg->cm_fields['E']);
00125 
00126        /* See if we can retrieve the previous version. */
00127        old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
00128        if (old_msgnum > 0L) {
00129               old_msg = CtdlFetchMessage(old_msgnum, 1);
00130        }
00131        else {
00132               old_msg = NULL;
00133        }
00134 
00135        if ((old_msg != NULL) && (old_msg->cm_fields['M'] == NULL)) {  /* old version is corrupt? */
00136               CtdlFreeMessage(old_msg);
00137               old_msg = NULL;
00138        }
00139        
00140        /* If no changes were made, don't bother saving it again */
00141        if ((old_msg != NULL) && (!strcmp(msg->cm_fields['M'], old_msg->cm_fields['M']))) {
00142               CtdlFreeMessage(old_msg);
00143               return(1);
00144        }
00145 
00146        /*
00147         * Generate diffs
00148         */
00149        CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
00150        CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
00151        CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
00152 
00153        if (old_msg != NULL) {
00154               fp = fopen(diff_old_filename, "w");
00155               rv = fwrite(old_msg->cm_fields['M'], strlen(old_msg->cm_fields['M']), 1, fp);
00156               fclose(fp);
00157               CtdlFreeMessage(old_msg);
00158        }
00159 
00160        fp = fopen(diff_new_filename, "w");
00161        rv = fwrite(msg->cm_fields['M'], strlen(msg->cm_fields['M']), 1, fp);
00162        fclose(fp);
00163 
00164        snprintf(diff_cmd, sizeof diff_cmd,
00165               DIFF " -u %s %s >%s",
00166               diff_new_filename,
00167               ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
00168               diff_out_filename
00169        );
00170        syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
00171        rv = system(diff_cmd);
00172        syslog(LOG_DEBUG, "diff cmd returned %d", rv);
00173 
00174        diffbuf_len = 0;
00175        diffbuf = NULL;
00176        fp = fopen(diff_out_filename, "r");
00177        if (fp == NULL) {
00178               fp = fopen("/dev/null", "r");
00179        }
00180        if (fp != NULL) {
00181               fseek(fp, 0L, SEEK_END);
00182               diffbuf_len = ftell(fp);
00183               fseek(fp, 0L, SEEK_SET);
00184               diffbuf = malloc(diffbuf_len + 1);
00185               fread(diffbuf, diffbuf_len, 1, fp);
00186               diffbuf[diffbuf_len] = 0;
00187               fclose(fp);
00188        }
00189 
00190        syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
00191 
00192        unlink(diff_old_filename);
00193        unlink(diff_new_filename);
00194        unlink(diff_out_filename);
00195 
00196        /* Determine whether this was a bogus (empty) edit */
00197        if ((diffbuf_len = 0) && (diffbuf != NULL)) {
00198               free(diffbuf);
00199               diffbuf = NULL;
00200        }
00201        if (diffbuf == NULL) {
00202               return(1);           /* No changes at all?  Abandon the post entirely! */
00203        }
00204 
00205        /* Now look for the existing edit history */
00206 
00207        history_msgnum = CtdlLocateMessageByEuid(history_page, &CCC->room);
00208        history_msg = NULL;
00209        if (history_msgnum > 0L) {
00210               history_msg = CtdlFetchMessage(history_msgnum, 1);
00211        }
00212 
00213        /* Create a new history message if necessary */
00214        if (history_msg == NULL) {
00215               history_msg = malloc(sizeof(struct CtdlMessage));
00216               memset(history_msg, 0, sizeof(struct CtdlMessage));
00217               history_msg->cm_magic = CTDLMESSAGE_MAGIC;
00218               history_msg->cm_anon_type = MES_NORMAL;
00219               history_msg->cm_format_type = FMT_RFC822;
00220               history_msg->cm_fields['A'] = strdup("Citadel");
00221               history_msg->cm_fields['R'] = strdup(CCC->room.QRname);
00222               history_msg->cm_fields['E'] = strdup(history_page);
00223               history_msg->cm_fields['U'] = strdup(history_page);
00224               history_msg->cm_fields['1'] = strdup("1");              /* suppress full text indexing */
00225               snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
00226               history_msg->cm_fields['M'] = malloc(1024);
00227               snprintf(history_msg->cm_fields['M'], 1024,
00228                      "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
00229                      "This is a Citadel wiki history encoded as multipart MIME.\n"
00230                      "Each part is comprised of a diff script representing one change set.\n"
00231                      "\n"
00232                      "--%s--\n"
00233                      ,
00234                      boundary, boundary
00235               );
00236        }
00237 
00238        /* Update the history message (regardless of whether it's new or existing) */
00239 
00240        /* Remove the Message-ID from the old version of the history message.  This will cause a brand
00241         * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
00242         */
00243        if (history_msg->cm_fields['I'] != NULL) {
00244               free(history_msg->cm_fields['I']);
00245               history_msg->cm_fields['I'] = NULL;
00246        }
00247 
00248        /* Figure out the boundary string.  We do this even when we generated the
00249         * boundary string in the above code, just to be safe and consistent.
00250         */
00251        strcpy(boundary, "");
00252 
00253        ptr = history_msg->cm_fields['M'];
00254        do {
00255               ptr = memreadline(ptr, buf, sizeof buf);
00256               if (*ptr != 0) {
00257                      striplt(buf);
00258                      if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
00259                             if (
00260                                    (bmstrcasestr(buf, "multipart") != NULL)
00261                                    && (bmstrcasestr(buf, "boundary=") != NULL)
00262                             ) {
00263                                    safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
00264                                    char *qu;
00265                                    qu = strchr(boundary, '\"');
00266                                    if (qu) {
00267                                           strcpy(boundary, ++qu);
00268                                    }
00269                                    qu = strchr(boundary, '\"');
00270                                    if (qu) {
00271                                           *qu = 0;
00272                                    }
00273                             }
00274                      }
00275               }
00276        } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
00277 
00278        /*
00279         * Now look for the first boundary.  That is where we need to insert our fun.
00280         */
00281        if (!IsEmptyStr(boundary)) {
00282               snprintf(prefixed_boundary, sizeof prefixed_boundary, "--%s", boundary);
00283               history_msg->cm_fields['M'] = realloc(history_msg->cm_fields['M'],
00284                      strlen(history_msg->cm_fields['M']) + strlen(diffbuf) + 1024
00285               );
00286               ptr = bmstrcasestr(history_msg->cm_fields['M'], prefixed_boundary);
00287               if (ptr != NULL) {
00288                      char *the_rest_of_it = strdup(ptr);
00289                      char uuid[64];
00290                      char memo[512];
00291                      char encoded_memo[1024];
00292                      generate_uuid(uuid);
00293                      snprintf(memo, sizeof memo, "%s|%ld|%s|%s", 
00294                             uuid,
00295                             time(NULL),
00296                             CCC->user.fullname,
00297                             config.c_nodename
00298                      );
00299                      CtdlEncodeBase64(encoded_memo, memo, strlen(memo), 0);
00300                      sprintf(ptr, "--%s\n"
00301                                    "Content-type: text/plain\n"
00302                                    "Content-Disposition: inline; filename=\"%s\"\n"
00303                                    "Content-Transfer-Encoding: 8bit\n"
00304                                    "\n"
00305                                    "%s\n"
00306                                    "%s"
00307                                    ,
00308                             boundary,
00309                             encoded_memo,
00310                             diffbuf,
00311                             the_rest_of_it
00312                      );
00313                      free(the_rest_of_it);
00314               }
00315 
00316               history_msg->cm_fields['T'] = realloc(history_msg->cm_fields['T'], 32);
00317               if (history_msg->cm_fields['T'] != NULL) {
00318                      snprintf(history_msg->cm_fields['T'], 32, "%ld", time(NULL));
00319               }
00320        
00321               CtdlSubmitMsg(history_msg, NULL, "", 0);
00322        }
00323        else {
00324               syslog(LOG_ALERT, "Empty boundary string in history message.  No history!\n");
00325        }
00326 
00327        free(diffbuf);
00328        free(history_msg);
00329        return(0);
00330 }
00331 
00332 
00333 /*
00334  * MIME Parser callback for wiki_history()
00335  *
00336  * The "filename" field will contain a memo field.  All we have to do is decode
00337  * the base64 and output it.  The data is already in a delimited format suitable
00338  * for our client protocol.
00339  */
00340 void wiki_history_callback(char *name, char *filename, char *partnum, char *disp,
00341                  void *content, char *cbtype, char *cbcharset, size_t length,
00342                  char *encoding, char *cbid, void *cbuserdata)
00343 {
00344        char memo[1024];
00345 
00346        CtdlDecodeBase64(memo, filename, strlen(filename));
00347        cprintf("%s\n", memo);
00348 }
00349 
00350 
00351 /*
00352  * Fetch a list of revisions for a particular wiki page
00353  */
00354 void wiki_history(char *pagename) {
00355        int r;
00356        char history_page_name[270];
00357        long msgnum;
00358        struct CtdlMessage *msg;
00359 
00360        r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
00361        if (r != om_ok) {
00362               if (r == om_not_logged_in) {
00363                      cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
00364               }
00365               else {
00366                      cprintf("%d An unknown error has occurred.\n", ERROR);
00367               }
00368               return;
00369        }
00370 
00371        snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
00372        msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
00373        if (msgnum > 0L) {
00374               msg = CtdlFetchMessage(msgnum, 1);
00375        }
00376        else {
00377               msg = NULL;
00378        }
00379 
00380        if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
00381               CtdlFreeMessage(msg);
00382               msg = NULL;
00383        }
00384 
00385        if (msg == NULL) {
00386               cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
00387               return;
00388        }
00389 
00390        
00391        cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
00392        mime_parser(msg->cm_fields['M'], NULL, *wiki_history_callback, NULL, NULL, NULL, 0);
00393        cprintf("000\n");
00394 
00395        CtdlFreeMessage(msg);
00396        return;
00397 }
00398 
00399 /*
00400  * MIME Parser callback for wiki_rev()
00401  *
00402  * The "filename" field will contain a memo field, which includes (among other things)
00403  * the uuid of this revision.  After we hit the desired revision, we stop processing.
00404  *
00405  * The "content" filed will contain "diff" output suitable for applying via "patch"
00406  * to our temporary file.
00407  */
00408 void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp,
00409                  void *content, char *cbtype, char *cbcharset, size_t length,
00410                  char *encoding, char *cbid, void *cbuserdata)
00411 {
00412        struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
00413        char memo[1024];
00414        char this_rev[256];
00415        FILE *fp;
00416        char *ptr = NULL;
00417        char buf[1024];
00418 
00419        /* Did a previous callback already indicate that we've reached our target uuid?
00420         * If so, don't process anything else.
00421         */
00422        if (hecbd->done) {
00423               return;
00424        }
00425 
00426        CtdlDecodeBase64(memo, filename, strlen(filename));
00427        extract_token(this_rev, memo, 0, '|', sizeof this_rev);
00428        striplt(this_rev);
00429 
00430        /* Perform the patch */
00431        fp = popen(PATCH " -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
00432        if (fp) {
00433               /* Replace the filenames in the patch with the tempfilename we're actually tweaking */
00434               fprintf(fp, "--- %s\n", hecbd->tempfilename);
00435               fprintf(fp, "+++ %s\n", hecbd->tempfilename);
00436 
00437               ptr = (char *)content;
00438               int linenum = 0;
00439               do {
00440                      ++linenum;
00441                      ptr = memreadline(ptr, buf, sizeof buf);
00442                      if (*ptr != 0) {
00443                             if (linenum <= 2) {
00444                                    /* skip the first two lines; they contain bogus filenames */
00445                             }
00446                             else {
00447                                    fprintf(fp, "%s\n", buf);
00448                             }
00449                      }
00450               } while ((*ptr != 0) && (ptr < ((char*)content + length)));
00451               if (pclose(fp) != 0) {
00452                      syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
00453               }
00454        }
00455 
00456        if (!strcasecmp(this_rev, hecbd->stop_when)) {
00457               /* Found our target rev.  Tell any subsequent callbacks to suppress processing. */
00458               syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
00459               hecbd->done = 1;
00460        }
00461 }
00462 
00463 
00464 /*
00465  * Fetch a specific revision of a wiki page.  The "operation" string may be set to "fetch" in order
00466  * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert"
00467  * to revert the currently active page to that revision.
00468  */
00469 void wiki_rev(char *pagename, char *rev, char *operation)
00470 {
00471        int r;
00472        char history_page_name[270];
00473        long msgnum;
00474        char temp[PATH_MAX];
00475        char timestamp[64];
00476        struct CtdlMessage *msg;
00477        FILE *fp;
00478        struct HistoryEraserCallBackData hecbd;
00479        long len = 0L;
00480        int rv;
00481 
00482        r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
00483        if (r != om_ok) {
00484               if (r == om_not_logged_in) {
00485                      cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
00486               }
00487               else {
00488                      cprintf("%d An unknown error has occurred.\n", ERROR);
00489               }
00490               return;
00491        }
00492 
00493        if (!strcasecmp(operation, "revert")) {
00494               r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, NULL, POST_LOGGED_IN, 0);
00495               if (r != 0) {
00496                      cprintf("%d %s\n", r, temp);
00497                      return;
00498               }
00499        }
00500 
00501        /* Begin by fetching the current version of the page.  We're going to patch
00502         * backwards through the diffs until we get the one we want.
00503         */
00504        msgnum = CtdlLocateMessageByEuid(pagename, &CC->room);
00505        if (msgnum > 0L) {
00506               msg = CtdlFetchMessage(msgnum, 1);
00507        }
00508        else {
00509               msg = NULL;
00510        }
00511 
00512        if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
00513               CtdlFreeMessage(msg);
00514               msg = NULL;
00515        }
00516 
00517        if (msg == NULL) {
00518               cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
00519               return;
00520        }
00521 
00522        /* Output it to a temporary file */
00523 
00524        CtdlMakeTempFileName(temp, sizeof temp);
00525        fp = fopen(temp, "w");
00526        if (fp != NULL) {
00527               r = fwrite(msg->cm_fields['M'], strlen(msg->cm_fields['M']), 1, fp);
00528               fclose(fp);
00529        }
00530        else {
00531               syslog(LOG_ALERT, "Cannot open %s: %s\n", temp, strerror(errno));
00532        }
00533        CtdlFreeMessage(msg);
00534 
00535        /* Get the revision history */
00536 
00537        snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
00538        msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
00539        if (msgnum > 0L) {
00540               msg = CtdlFetchMessage(msgnum, 1);
00541        }
00542        else {
00543               msg = NULL;
00544        }
00545 
00546        if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
00547               CtdlFreeMessage(msg);
00548               msg = NULL;
00549        }
00550 
00551        if (msg == NULL) {
00552               cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
00553               return;
00554        }
00555 
00556        /* Start patching backwards (newest to oldest) through the revision history until we
00557         * hit the revision uuid requested by the user.  (The callback will perform each one.)
00558         */
00559 
00560        memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
00561        hecbd.tempfilename = temp;
00562        hecbd.stop_when = rev;
00563        striplt(hecbd.stop_when);
00564 
00565        mime_parser(msg->cm_fields['M'], NULL, *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
00566        CtdlFreeMessage(msg);
00567 
00568        /* Were we successful? */
00569        if (hecbd.done == 0) {
00570               cprintf("%d Revision '%s' of page '%s' was not found.\n",
00571                      ERROR + MESSAGE_NOT_FOUND, rev, pagename
00572               );
00573        }
00574 
00575        /* We have the desired revision on disk.  Now do something with it. */
00576 
00577        else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
00578               msg = malloc(sizeof(struct CtdlMessage));
00579               memset(msg, 0, sizeof(struct CtdlMessage));
00580               msg->cm_magic = CTDLMESSAGE_MAGIC;
00581               msg->cm_anon_type = MES_NORMAL;
00582               msg->cm_format_type = FMT_RFC822;
00583               fp = fopen(temp, "r");
00584               if (fp) {
00585                      fseek(fp, 0L, SEEK_END);
00586                      len = ftell(fp);
00587                      fseek(fp, 0L, SEEK_SET);
00588                      msg->cm_fields['M'] = malloc(len + 1);
00589                      rv = fread(msg->cm_fields['M'], len, 1, fp);
00590                      syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
00591                      msg->cm_fields['M'][len] = 0;
00592                      fclose(fp);
00593               }
00594               if (len <= 0) {
00595                      msgnum = (-1L);
00596               }
00597               else if (!strcasecmp(operation, "fetch")) {
00598                      msg->cm_fields['A'] = strdup("Citadel");
00599                      CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS);   /* Not an error if already exists */
00600                      msgnum = CtdlSubmitMsg(msg, NULL, wwm, 0);       /* Store the revision here */
00601 
00602                      /*
00603                       * WARNING: VILE SLEAZY HACK
00604                       * This will avoid the 'message xxx is not in this room' security error,
00605                       * but only if the client fetches the message we just generated immediately
00606                       * without first trying to perform other fetch operations.
00607                       */
00608                      if (CC->cached_msglist != NULL) {
00609                             free(CC->cached_msglist);
00610                             CC->cached_msglist = NULL;
00611                             CC->cached_num_msgs = 0;
00612                      }
00613                      CC->cached_msglist = malloc(sizeof(long));
00614                      if (CC->cached_msglist != NULL) {
00615                             CC->cached_num_msgs = 1;
00616                             CC->cached_msglist[0] = msgnum;
00617                      }
00618 
00619               }
00620               else if (!strcasecmp(operation, "revert")) {
00621                      snprintf(timestamp, sizeof timestamp, "%ld", time(NULL));
00622                      msg->cm_fields['T'] = strdup(timestamp);
00623                      msg->cm_fields['A'] = strdup(CC->user.fullname);
00624                      msg->cm_fields['F'] = strdup(CC->cs_inet_email);
00625                      msg->cm_fields['O'] = strdup(CC->room.QRname);
00626                      msg->cm_fields['N'] = strdup(NODENAME);
00627                      msg->cm_fields['E'] = strdup(pagename);
00628                      msgnum = CtdlSubmitMsg(msg, NULL, "", 0); /* Replace the current revision */
00629               }
00630               else {
00631                      /* Theoretically it is impossible to get here, but throw an error anyway */
00632                      msgnum = (-1L);
00633               }
00634               CtdlFreeMessage(msg);
00635               if (msgnum >= 0L) {
00636                      cprintf("%d %ld\n", CIT_OK, msgnum);             /* Give the client a msgnum */
00637               }
00638               else {
00639                      cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
00640               }
00641        }
00642 
00643        /* We did all this work for nothing.  Express anguish to the caller. */
00644        else {
00645               cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
00646        }
00647 
00648        unlink(temp);
00649        return;
00650 }
00651 
00652 
00653 
00654 /*
00655  * commands related to wiki management
00656  */
00657 void cmd_wiki(char *argbuf) {
00658        char subcmd[32];
00659        char pagename[256];
00660        char rev[128];
00661        char operation[16];
00662 
00663        extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
00664 
00665        if (!strcasecmp(subcmd, "history")) {
00666               extract_token(pagename, argbuf, 1, '|', sizeof pagename);
00667               wiki_history(pagename);
00668               return;
00669        }
00670 
00671        if (!strcasecmp(subcmd, "rev")) {
00672               extract_token(pagename, argbuf, 1, '|', sizeof pagename);
00673               extract_token(rev, argbuf, 2, '|', sizeof rev);
00674               extract_token(operation, argbuf, 3, '|', sizeof operation);
00675               wiki_rev(pagename, rev, operation);
00676               return;
00677        }
00678 
00679        cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
00680 }
00681 
00682 
00683 
00684 /*
00685  * Module initialization
00686  */
00687 CTDL_MODULE_INIT(wiki)
00688 {
00689        if (!threading)
00690        {
00691               CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
00692               CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
00693        }
00694 
00695        /* return our module name for the log */
00696        return "wiki";
00697 }