citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

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