Back to index

citadel  8.12
Classes | Functions | Variables
serv_wiki.c File Reference
#include "sysdep.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <time.h>
#include <sys/wait.h>
#include <string.h>
#include <limits.h>
#include <libcitadel.h>
#include "citadel.h"
#include "server.h"
#include "citserver.h"
#include "support.h"
#include "config.h"
#include "control.h"
#include "user_ops.h"
#include "database.h"
#include "msgbase.h"
#include "euidindex.h"
#include "ctdl_module.h"

Go to the source code of this file.

Classes

struct  HistoryEraserCallBackData

Functions

int wiki_upload_beforesave (struct CtdlMessage *msg)
void wiki_history_callback (char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
void wiki_history (char *pagename)
void wiki_rev_callback (char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
void wiki_rev (char *pagename, char *rev, char *operation)
void cmd_wiki (char *argbuf)
 CTDL_MODULE_INIT (wiki)

Variables

char * wwm = "9999999999.WikiWaybackMachine"

Class Documentation

struct HistoryEraserCallBackData

Definition at line 56 of file serv_wiki.c.

Class Members
int done
char * stop_when
char * tempfilename

Function Documentation

void cmd_wiki ( char *  argbuf)

Definition at line 657 of file serv_wiki.c.

                            {
       char subcmd[32];
       char pagename[256];
       char rev[128];
       char operation[16];

       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);

       if (!strcasecmp(subcmd, "history")) {
              extract_token(pagename, argbuf, 1, '|', sizeof pagename);
              wiki_history(pagename);
              return;
       }

       if (!strcasecmp(subcmd, "rev")) {
              extract_token(pagename, argbuf, 1, '|', sizeof pagename);
              extract_token(rev, argbuf, 2, '|', sizeof rev);
              extract_token(operation, argbuf, 3, '|', sizeof operation);
              wiki_rev(pagename, rev, operation);
              return;
       }

       cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
}

Here is the call graph for this function:

Here is the caller graph for this function:

CTDL_MODULE_INIT ( wiki  )

Definition at line 687 of file serv_wiki.c.

{
       if (!threading)
       {
              CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
              CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
       }

       /* return our module name for the log */
       return "wiki";
}

Here is the call graph for this function:

void wiki_history ( char *  pagename)

Definition at line 354 of file serv_wiki.c.

                                  {
       int r;
       char history_page_name[270];
       long msgnum;
       struct CtdlMessage *msg;

       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
       if (r != om_ok) {
              if (r == om_not_logged_in) {
                     cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
              }
              else {
                     cprintf("%d An unknown error has occurred.\n", ERROR);
              }
              return;
       }

       snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
       msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
       if (msgnum > 0L) {
              msg = CtdlFetchMessage(msgnum, 1);
       }
       else {
              msg = NULL;
       }

       if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
              CtdlFreeMessage(msg);
              msg = NULL;
       }

       if (msg == NULL) {
              cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
              return;
       }

       
       cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
       mime_parser(msg->cm_fields['M'], NULL, *wiki_history_callback, NULL, NULL, NULL, 0);
       cprintf("000\n");

       CtdlFreeMessage(msg);
       return;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void wiki_history_callback ( char *  name,
char *  filename,
char *  partnum,
char *  disp,
void *  content,
char *  cbtype,
char *  cbcharset,
size_t  length,
char *  encoding,
char *  cbid,
void *  cbuserdata 
)

Definition at line 340 of file serv_wiki.c.

{
       char memo[1024];

       CtdlDecodeBase64(memo, filename, strlen(filename));
       cprintf("%s\n", memo);
}

Here is the call graph for this function:

Here is the caller graph for this function:

void wiki_rev ( char *  pagename,
char *  rev,
char *  operation 
)

Definition at line 469 of file serv_wiki.c.

{
       int r;
       char history_page_name[270];
       long msgnum;
       char temp[PATH_MAX];
       char timestamp[64];
       struct CtdlMessage *msg;
       FILE *fp;
       struct HistoryEraserCallBackData hecbd;
       long len = 0L;
       int rv;

       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
       if (r != om_ok) {
              if (r == om_not_logged_in) {
                     cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
              }
              else {
                     cprintf("%d An unknown error has occurred.\n", ERROR);
              }
              return;
       }

       if (!strcasecmp(operation, "revert")) {
              r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, NULL, POST_LOGGED_IN, 0);
              if (r != 0) {
                     cprintf("%d %s\n", r, temp);
                     return;
              }
       }

       /* 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);
       if (msgnum > 0L) {
              msg = CtdlFetchMessage(msgnum, 1);
       }
       else {
              msg = NULL;
       }

       if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
              CtdlFreeMessage(msg);
              msg = NULL;
       }

       if (msg == NULL) {
              cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
              return;
       }

       /* Output it to a temporary file */

       CtdlMakeTempFileName(temp, sizeof temp);
       fp = fopen(temp, "w");
       if (fp != NULL) {
              r = fwrite(msg->cm_fields['M'], strlen(msg->cm_fields['M']), 1, fp);
              fclose(fp);
       }
       else {
              syslog(LOG_ALERT, "Cannot open %s: %s\n", temp, strerror(errno));
       }
       CtdlFreeMessage(msg);

       /* Get the revision history */

       snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
       msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
       if (msgnum > 0L) {
              msg = CtdlFetchMessage(msgnum, 1);
       }
       else {
              msg = NULL;
       }

       if ((msg != NULL) && (msg->cm_fields['M'] == NULL)) {
              CtdlFreeMessage(msg);
              msg = NULL;
       }

       if (msg == NULL) {
              cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
              return;
       }

       /* Start patching backwards (newest to oldest) through the revision history until we
        * hit the revision uuid requested by the user.  (The callback will perform each one.)
        */

       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);

       /* Were we successful? */
       if (hecbd.done == 0) {
              cprintf("%d Revision '%s' of page '%s' was not found.\n",
                     ERROR + MESSAGE_NOT_FOUND, rev, pagename
              );
       }

       /* We have the desired revision on disk.  Now do something with it. */

       else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
              msg = malloc(sizeof(struct CtdlMessage));
              memset(msg, 0, sizeof(struct CtdlMessage));
              msg->cm_magic = CTDLMESSAGE_MAGIC;
              msg->cm_anon_type = MES_NORMAL;
              msg->cm_format_type = FMT_RFC822;
              fp = fopen(temp, "r");
              if (fp) {
                     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);
                     syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
                     msg->cm_fields['M'][len] = 0;
                     fclose(fp);
              }
              if (len <= 0) {
                     msgnum = (-1L);
              }
              else if (!strcasecmp(operation, "fetch")) {
                     msg->cm_fields['A'] = strdup("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 */

                     /*
                      * 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 (CC->cached_msglist != NULL) {
                            free(CC->cached_msglist);
                            CC->cached_msglist = NULL;
                            CC->cached_num_msgs = 0;
                     }
                     CC->cached_msglist = malloc(sizeof(long));
                     if (CC->cached_msglist != NULL) {
                            CC->cached_num_msgs = 1;
                            CC->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 */
              }
              else {
                     /* Theoretically it is impossible to get here, but throw an error anyway */
                     msgnum = (-1L);
              }
              CtdlFreeMessage(msg);
              if (msgnum >= 0L) {
                     cprintf("%d %ld\n", CIT_OK, msgnum);             /* Give the client a msgnum */
              }
              else {
                     cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
              }
       }

       /* We did all this work for nothing.  Express anguish to the caller. */
       else {
              cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
       }

       unlink(temp);
       return;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void wiki_rev_callback ( char *  name,
char *  filename,
char *  partnum,
char *  disp,
void *  content,
char *  cbtype,
char *  cbcharset,
size_t  length,
char *  encoding,
char *  cbid,
void *  cbuserdata 
)

Definition at line 408 of file serv_wiki.c.

{
       struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
       char memo[1024];
       char this_rev[256];
       FILE *fp;
       char *ptr = NULL;
       char buf[1024];

       /* Did a previous callback already indicate that we've reached our target uuid?
        * If so, don't process anything else.
        */
       if (hecbd->done) {
              return;
       }

       CtdlDecodeBase64(memo, filename, strlen(filename));
       extract_token(this_rev, memo, 0, '|', sizeof this_rev);
       striplt(this_rev);

       /* Perform the patch */
       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);
              fprintf(fp, "+++ %s\n", hecbd->tempfilename);

              ptr = (char *)content;
              int linenum = 0;
              do {
                     ++linenum;
                     ptr = memreadline(ptr, buf, sizeof buf);
                     if (*ptr != 0) {
                            if (linenum <= 2) {
                                   /* skip the first two lines; they contain bogus filenames */
                            }
                            else {
                                   fprintf(fp, "%s\n", buf);
                            }
                     }
              } while ((*ptr != 0) && (ptr < ((char*)content + length)));
              if (pclose(fp) != 0) {
                     syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
              }
       }

       if (!strcasecmp(this_rev, hecbd->stop_when)) {
              /* Found our target rev.  Tell any subsequent callbacks to suppress processing. */
              syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
              hecbd->done = 1;
       }
}

Here is the caller graph for this function:

int wiki_upload_beforesave ( struct CtdlMessage msg)

Definition at line 72 of file serv_wiki.c.

                                                    {
       struct CitContext *CCC = CC;
       long old_msgnum = (-1L);
       struct CtdlMessage *old_msg = NULL;
       long history_msgnum = (-1L);
       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];
       char boundary[256];
       char prefixed_boundary[258];
       char buf[1024];
       char *diffbuf = NULL;
       size_t diffbuf_len = 0;
       char *ptr = NULL;

       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) {
              return(0);
       }

       /* If this isn't a MIME message, don't bother. */
       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);

       snprintf(history_page, sizeof history_page, "%s_HISTORY_", msg->cm_fields['E']);

       /* 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_"))
       ) {
              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);

       /* 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']);

       /* See if we can retrieve the previous version. */
       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
       if (old_msgnum > 0L) {
              old_msg = CtdlFetchMessage(old_msgnum, 1);
       }
       else {
              old_msg = NULL;
       }

       if ((old_msg != NULL) && (old_msg->cm_fields['M'] == NULL)) {  /* old version is corrupt? */
              CtdlFreeMessage(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);
              return(1);
       }

       /*
        * Generate diffs
        */
       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);
              fclose(fp);
              CtdlFreeMessage(old_msg);
       }

       fp = fopen(diff_new_filename, "w");
       rv = fwrite(msg->cm_fields['M'], strlen(msg->cm_fields['M']), 1, fp);
       fclose(fp);

       snprintf(diff_cmd, sizeof diff_cmd,
              DIFF " -u %s %s >%s",
              diff_new_filename,
              ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
              diff_out_filename
       );
       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) {
              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 "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)) {
              free(diffbuf);
              diffbuf = NULL;
       }
       if (diffbuf == NULL) {
              return(1);           /* No changes at all?  Abandon the post entirely! */
       }

       /* Now look for the existing edit history */

       history_msgnum = CtdlLocateMessageByEuid(history_page, &CCC->room);
       history_msg = NULL;
       if (history_msgnum > 0L) {
              history_msg = CtdlFetchMessage(history_msgnum, 1);
       }

       /* Create a new history message if necessary */
       if (history_msg == NULL) {
              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 */
              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
              );
       }

       /* 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;
       }

       /* 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, "");

       ptr = history_msg->cm_fields['M'];
       do {
              ptr = memreadline(ptr, buf, sizeof buf);
              if (*ptr != 0) {
                     striplt(buf);
                     if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
                            if (
                                   (bmstrcasestr(buf, "multipart") != NULL)
                                   && (bmstrcasestr(buf, "boundary=") != NULL)
                            ) {
                                   safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
                                   char *qu;
                                   qu = strchr(boundary, '\"');
                                   if (qu) {
                                          strcpy(boundary, ++qu);
                                   }
                                   qu = strchr(boundary, '\"');
                                   if (qu) {
                                          *qu = 0;
                                   }
                            }
                     }
              }
       } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );

       /*
        * 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);
              if (ptr != NULL) {
                     char *the_rest_of_it = strdup(ptr);
                     char uuid[64];
                     char memo[512];
                     char encoded_memo[1024];
                     generate_uuid(uuid);
                     snprintf(memo, sizeof memo, "%s|%ld|%s|%s", 
                            uuid,
                            time(NULL),
                            CCC->user.fullname,
                            config.c_nodename
                     );
                     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);
              }

              history_msg->cm_fields['T'] = realloc(history_msg->cm_fields['T'], 32);
              if (history_msg->cm_fields['T'] != NULL) {
                     snprintf(history_msg->cm_fields['T'], 32, "%ld", time(NULL));
              }
       
              CtdlSubmitMsg(history_msg, NULL, "", 0);
       }
       else {
              syslog(LOG_ALERT, "Empty boundary string in history message.  No history!\n");
       }

       free(diffbuf);
       free(history_msg);
       return(0);
}

Here is the call graph for this function:

Here is the caller graph for this function:


Variable Documentation

char* wwm = "9999999999.WikiWaybackMachine"

Definition at line 66 of file serv_wiki.c.