96ff75057f20e218285c7f1820416f6d82539961
[citadel.git] / citadel / modules / migrate / serv_migrate.c
1 /*
2  * This module dumps and/or loads the Citadel database in XML format.
3  *
4  * Copyright (c) 1987-2018 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * 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 /*
16  * Explanation of <progress> tags:
17  *
18  * 0%              nothing
19  * 2%              finished exporting configuration
20  * 7%              finished exporting users
21  * 12%             finished exporting openids
22  * 17%             finished exporting rooms
23  * 18%             finished exporting floors
24  * 25%             finished exporting visits
25  * 100%            finished exporting messages
26  */
27
28 #include "sysdep.h"
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <stdio.h>
32 #include <fcntl.h>
33 #include <signal.h>
34 #include <pwd.h>
35 #include <errno.h>
36 #include <sys/types.h>
37
38 #if TIME_WITH_SYS_TIME
39 # include <sys/time.h>
40 # include <time.h>
41 #else
42 # if HAVE_SYS_TIME_H
43 #  include <sys/time.h>
44 # else
45 #  include <time.h>
46 # endif
47 #endif
48
49 #include <sys/wait.h>
50 #include <string.h>
51 #include <ctype.h>
52 #include <limits.h>
53 #include <expat.h>
54 #include <libcitadel.h>
55 #include "citadel.h"
56 #include "server.h"
57 #include "citserver.h"
58 #include "support.h"
59 #include "config.h"
60 #include "database.h"
61 #include "msgbase.h"
62 #include "user_ops.h"
63 #include "euidindex.h"
64 #include "ctdl_module.h"
65
66 #define END_OF_MESSAGE  "---eom---dbd---"
67
68 char migr_tempfilename1[PATH_MAX];
69 char migr_tempfilename2[PATH_MAX];
70 FILE *migr_global_message_list;
71 int total_msgs = 0;
72 char *ikey = NULL;                      // If we're importing a config key we store it here.
73
74
75 /******************************************************************************
76  *        Code which implements the export appears in this section            *
77  ******************************************************************************/
78
79 /*
80  * Output a string to the client with these characters escaped:  & < > " '
81  */
82 void xml_strout(char *str) {
83
84         char *c = str;
85
86         if (str == NULL) {
87                 return;
88         }
89
90         while (*c != 0) {
91                 if (*c == '\"') {
92                         client_write(HKEY("&quot;"));
93                 }
94                 else if (*c == '\'') {
95                         client_write(HKEY("&apos;"));
96                 }
97                 else if (*c == '<') {
98                         client_write(HKEY("&lt;"));
99                 }
100                 else if (*c == '>') {
101                         client_write(HKEY("&gt;"));
102                 }
103                 else if (*c == '&') {
104                         client_write(HKEY("&amp;"));
105                 }
106                 else {
107                         client_write(c, 1);
108                 }
109                 ++c;
110         }
111 }
112
113
114 /*
115  * Export a user record as XML
116  */
117 void migr_export_users_backend(struct ctdluser *buf, void *data) {
118         client_write(HKEY("<user>\n"));
119         cprintf("<u_version>%d</u_version>\n", buf->version);
120         cprintf("<u_uid>%ld</u_uid>\n", (long)buf->uid);
121         client_write(HKEY("<u_password>"));     xml_strout(buf->password);              client_write(HKEY("</u_password>\n"));
122         cprintf("<u_flags>%u</u_flags>\n", buf->flags);
123         cprintf("<u_timescalled>%ld</u_timescalled>\n", buf->timescalled);
124         cprintf("<u_posted>%ld</u_posted>\n", buf->posted);
125         cprintf("<u_axlevel>%d</u_axlevel>\n", buf->axlevel);
126         cprintf("<u_usernum>%ld</u_usernum>\n", buf->usernum);
127         cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)buf->lastcall);
128         cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", buf->USuserpurge);
129         client_write(HKEY("<u_fullname>"));     xml_strout(buf->fullname);              client_write(HKEY("</u_fullname>\n"));
130         cprintf("<u_msgnum_bio>%ld</u_msgnum_bio>\n", buf->msgnum_bio);
131         cprintf("<u_msgnum_pic>%ld</u_msgnum_pic>\n", buf->msgnum_pic);
132         client_write(HKEY("</user>\n"));
133 }
134
135
136 void migr_export_users(void) {
137         ForEachUser(migr_export_users_backend, NULL);
138 }
139
140
141 void migr_export_room_msg(long msgnum, void *userdata) {
142         cprintf("%ld\n", msgnum);
143         fprintf(migr_global_message_list, "%ld\n", msgnum);
144 }
145
146
147 void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
148         client_write(HKEY("<room>\n"));
149         client_write(HKEY("<QRname>")); xml_strout(buf->QRname);        client_write(HKEY("</QRname>\n"));
150         client_write(HKEY("<QRpasswd>"));       xml_strout(buf->QRpasswd);      client_write(HKEY("</QRpasswd>\n"));
151         cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
152         cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
153         cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
154         cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
155         if (buf->QRflags & QR_DIRECTORY) {
156                 client_write(HKEY("<QRdirname>"));
157                 xml_strout(buf->QRdirname);
158                 client_write(HKEY("</QRdirname>\n"));
159         }
160         cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
161         cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
162         cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
163         cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
164         cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
165         cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
166         cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
167         cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
168         cprintf("<msgnum_info>%ld</msgnum_info>\n", buf->msgnum_info);
169         cprintf("<msgnum_pic>%ld</msgnum_pic>\n", buf->msgnum_pic);
170         client_write(HKEY("</room>\n"));
171
172         /* message list goes inside this tag */
173
174         CtdlGetRoom(&CC->room, buf->QRname);
175         client_write(HKEY("<room_messages>"));
176         client_write(HKEY("<FRname>")); xml_strout(CC->room.QRname);    client_write(HKEY("</FRname>\n"));
177         client_write(HKEY("<FRmsglist>"));
178         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
179         client_write(HKEY("</FRmsglist>\n"));
180         client_write(HKEY("</room_messages>\n"));
181
182
183 }
184
185
186 void migr_export_rooms(void) {
187         char cmd[SIZ];
188         migr_global_message_list = fopen(migr_tempfilename1, "w");
189         if (migr_global_message_list != NULL) {
190                 CtdlForEachRoom(migr_export_rooms_backend, NULL);
191                 fclose(migr_global_message_list);
192         }
193
194         /*
195          * Process the 'global' message list.  (Sort it and remove dups.
196          * Dups are ok because a message may be in more than one room, but
197          * this will be handled by exporting the reference count, not by
198          * exporting the message multiple times.)
199          */
200         snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
201         if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d", errno);
202         snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
203         if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d", errno);
204
205
206         snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1);
207         FILE *fp = popen(cmd, "r");
208         if (fp) {
209                 fgets(cmd, sizeof cmd, fp);
210                 pclose(fp);
211                 total_msgs = atoi(cmd);
212         }
213         else {
214                 total_msgs = 1; // any nonzero just to keep it from barfing
215         }
216         syslog(LOG_DEBUG, "Total messages to be exported: %d", total_msgs);
217 }
218
219
220 void migr_export_floors(void) {
221         struct floor qfbuf, *buf;
222         int i;
223
224         for (i=0; i < MAXFLOORS; ++i) {
225                 client_write(HKEY("<floor>\n"));
226                 cprintf("<f_num>%d</f_num>\n", i);
227                 CtdlGetFloor(&qfbuf, i);
228                 buf = &qfbuf;
229                 cprintf("<f_flags>%u</f_flags>\n", buf->f_flags);
230                 client_write(HKEY("<f_name>")); xml_strout(buf->f_name); client_write(HKEY("</f_name>\n"));
231                 cprintf("<f_ref_count>%d</f_ref_count>\n", buf->f_ref_count);
232                 cprintf("<f_ep_expire_mode>%d</f_ep_expire_mode>\n", buf->f_ep.expire_mode);
233                 cprintf("<f_ep_expire_value>%d</f_ep_expire_value>\n", buf->f_ep.expire_value);
234                 client_write(HKEY("</floor>\n"));
235         }
236 }
237
238
239 /*
240  * Return nonzero if the supplied string contains only characters which are valid in a sequence set.
241  */
242 int is_sequence_set(char *s) {
243         if (!s) return(0);
244
245         char *c = s;
246         char ch;
247         while (ch = *c++, ch) {
248                 if (!strchr("0123456789*,:", ch)) {
249                         return(0);
250                 }
251         }
252         return(1);
253 }
254
255
256
257 /* 
258  *  Traverse the visits file...
259  */
260 void migr_export_visits(void) {
261         visit vbuf;
262         struct cdbdata *cdbv;
263
264         cdb_rewind(CDB_VISIT);
265
266         while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
267                 memset(&vbuf, 0, sizeof(visit));
268                 memcpy(&vbuf, cdbv->ptr,
269                        ((cdbv->len > sizeof(visit)) ?
270                         sizeof(visit) : cdbv->len));
271                 cdb_free(cdbv);
272
273                 client_write(HKEY("<visit>\n"));
274                 cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
275                 cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
276                 cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
277
278                 client_write(HKEY("<v_seen>"));
279                 if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
280                         xml_strout(vbuf.v_seen);
281                 }
282                 else {
283                         cprintf("%ld", vbuf.v_lastseen);
284                 }
285                 client_write(HKEY("</v_seen>"));
286
287                 if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
288                         client_write(HKEY("<v_answered>"));
289                         xml_strout(vbuf.v_answered);
290                         client_write(HKEY("</v_answered>\n"));
291                 }
292
293                 cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
294                 cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
295                 client_write(HKEY("</visit>\n"));
296         }
297 }
298
299
300 void migr_export_message(long msgnum) {
301         struct MetaData smi;
302         struct CtdlMessage *msg;
303         struct ser_ret smr;
304
305         /* We can use a static buffer here because there will never be more than
306          * one of this operation happening at any given time, and it's really best
307          * to just keep it allocated once instead of torturing malloc/free.
308          * Call this function with msgnum "-1" to free the buffer when finished.
309          */
310         static int encoded_alloc = 0;
311         static char *encoded_msg = NULL;
312
313         if (msgnum < 0) {
314                 if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
315                         free(encoded_msg);
316                         encoded_alloc = 0;
317                         encoded_msg = NULL;
318                 }
319                 return;
320         }
321
322         /* Ok, here we go ... */
323
324         msg = CtdlFetchMessage(msgnum, 1, 0);
325         if (msg == NULL) return;        /* fail silently */
326
327         client_write(HKEY("<message>\n"));
328         GetMetaData(&smi, msgnum);
329         cprintf("<msg_msgnum>%ld</msg_msgnum>\n", msgnum);
330         cprintf("<msg_meta_refcount>%d</msg_meta_refcount>\n", smi.meta_refcount);
331         cprintf("<msg_meta_rfc822_length>%ld</msg_meta_rfc822_length>\n", smi.meta_rfc822_length);
332         client_write(HKEY("<msg_meta_content_type>")); xml_strout(smi.meta_content_type); client_write(HKEY("</msg_meta_content_type>\n"));
333
334         client_write(HKEY("<msg_text>"));
335         CtdlSerializeMessage(&smr, msg);
336         CM_Free(msg);
337
338         /* Predict the buffer size we need.  Expand the buffer if necessary. */
339         int encoded_len = smr.len * 15 / 10 ;
340         if (encoded_len > encoded_alloc) {
341                 encoded_alloc = encoded_len;
342                 encoded_msg = realloc(encoded_msg, encoded_alloc);
343         }
344
345         if (encoded_msg == NULL) {
346                 /* Questionable hack that hopes it'll work next time and we only lose one message */
347                 encoded_alloc = 0;
348         }
349         else {
350                 /* Once we do the encoding we know the exact size */
351                 encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1);
352                 client_write(encoded_msg, encoded_len);
353         }
354
355         free(smr.ser);
356
357         client_write(HKEY("</msg_text>\n"));
358         client_write(HKEY("</message>\n"));
359 }
360
361
362
363 void migr_export_openids(void) {
364         struct cdbdata *cdboi;
365         long usernum;
366         char url[512];
367
368         cdb_rewind(CDB_EXTAUTH);
369         while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
370                 if (cdboi->len > sizeof(long)) {
371                         client_write(HKEY("<openid>\n"));
372                         memcpy(&usernum, cdboi->ptr, sizeof(long));
373                         snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
374                         client_write(HKEY("<oid_url>"));
375                         xml_strout(url);
376                         client_write(HKEY("</oid_url>\n"));
377                         cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
378                         client_write(HKEY("</openid>\n"));
379                 }
380                 cdb_free(cdboi);
381         }
382 }
383
384
385 void migr_export_configs(void) {
386         struct cdbdata *cdbcfg;
387         int keylen = 0;
388         char *key = NULL;
389         char *value = NULL;
390
391         cdb_rewind(CDB_CONFIG);
392         while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
393
394                 keylen = strlen(cdbcfg->ptr);
395                 key = cdbcfg->ptr;
396                 value = cdbcfg->ptr + keylen + 1;
397
398                 client_write("<config key=\"", 13);
399                 xml_strout(key);
400                 client_write("\">", 2);
401                 xml_strout(value);
402                 client_write("</config>\n", 10);
403                 cdb_free(cdbcfg);
404         }
405 }
406
407
408
409
410 void migr_export_messages(void) {
411         char buf[SIZ];
412         long msgnum;
413         int count = 0;
414         int progress = 0;
415         int prev_progress = 0;
416         CitContext *Ctx;
417
418         Ctx = CC;
419         migr_global_message_list = fopen(migr_tempfilename1, "r");
420         if (migr_global_message_list != NULL) {
421                 syslog(LOG_INFO, "Opened %s", migr_tempfilename1);
422                 while ((Ctx->kill_me == 0) && 
423                        (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
424                         msgnum = atol(buf);
425                         if (msgnum > 0L) {
426                                 migr_export_message(msgnum);
427                                 ++count;
428                         }
429                         progress = (count * 74 / total_msgs) + 25 ;
430                         if ((progress > prev_progress) && (progress < 100)) {
431                                 cprintf("<progress>%d</progress>\n", progress);
432                         }
433                         prev_progress = progress;
434                 }
435                 fclose(migr_global_message_list);
436         }
437         if (Ctx->kill_me == 0)
438                 syslog(LOG_INFO, "Exported %d messages.", count);
439         else
440                 syslog(LOG_ERR, "Export aborted due to client disconnect!");
441
442         migr_export_message(-1L);       /* This frees the encoding buffer */
443 }
444
445
446
447 void migr_do_export(void) {
448         CitContext *Ctx;
449
450         Ctx = CC;
451         cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
452         Ctx->dont_term = 1;
453
454         client_write(HKEY("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"));
455         client_write(HKEY("<citadel_migrate_data>\n"));
456         cprintf("<version>%d</version>\n", REV_LEVEL);
457         cprintf("<progress>%d</progress>\n", 0);
458
459         /* export the configuration database */
460         migr_export_configs();
461         cprintf("<progress>%d</progress>\n", 2);
462         
463         if (Ctx->kill_me == 0)  migr_export_users();
464         cprintf("<progress>%d</progress>\n", 7);
465         if (Ctx->kill_me == 0)  migr_export_openids();
466         cprintf("<progress>%d</progress>\n", 12);
467         if (Ctx->kill_me == 0)  migr_export_rooms();
468         cprintf("<progress>%d</progress>\n", 17);
469         if (Ctx->kill_me == 0)  migr_export_floors();
470         cprintf("<progress>%d</progress>\n", 18);
471         if (Ctx->kill_me == 0)  migr_export_visits();
472         cprintf("<progress>%d</progress>\n", 25);
473         if (Ctx->kill_me == 0)  migr_export_messages();
474         client_write(HKEY("</citadel_migrate_data>\n"));
475         cprintf("<progress>%d</progress>\n", 100);
476         client_write(HKEY("000\n"));
477         Ctx->dont_term = 0;
478 }
479
480
481
482
483 /******************************************************************************
484  *                              Import code                                   *
485  *    Here's the code that implements the import side.  It's going to end up  *
486  *        being one big loop with lots of global variables.  I don't care.    *
487  * You wouldn't run multiple concurrent imports anyway.  If this offends your *
488  * delicate sensibilities  then go rewrite it in Ruby on Rails or something.  *
489  ******************************************************************************/
490
491
492 int citadel_migrate_data = 0;           /* Are we inside a <citadel_migrate_data> tag pair? */
493 StrBuf *migr_chardata = NULL;
494 StrBuf *migr_MsgData = NULL;
495 struct ctdluser usbuf;
496 struct ctdlroom qrbuf;
497 char openid_url[512];
498 long openid_usernum = 0;
499 char FRname[ROOMNAMELEN];
500 struct floor flbuf;
501 int floornum = 0;
502 visit vbuf;
503 struct MetaData smi;
504 long import_msgnum = 0;
505
506 /*
507  * This callback stores up the data which appears in between tags.
508  */
509 void migr_xml_chardata(void *data, const XML_Char *s, int len)
510 {
511         StrBufAppendBufPlain(migr_chardata, s, len, 0);
512 }
513
514
515 void migr_xml_start(void *data, const char *el, const char **attr) {
516         int i;
517
518         /*** GENERAL STUFF ***/
519
520         /* Reset chardata_len to zero and init buffer */
521         FlushStrBuf(migr_chardata);
522         FlushStrBuf(migr_MsgData);
523
524         if (!strcasecmp(el, "citadel_migrate_data")) {
525                 ++citadel_migrate_data;
526
527                 /* As soon as it looks like the input data is a genuine Citadel XML export,
528                  * whack the existing database on disk to make room for the new one.
529                  */
530                 if (citadel_migrate_data == 1) {
531                         for (i = 0; i < MAXCDB; ++i) {
532                                 cdb_trunc(i);
533                         }
534                 }
535                 return;
536         }
537
538         if (citadel_migrate_data != 1) {
539                 syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected.  Warning: ODD-DATA!", el);
540                 return;
541         }
542
543         /* When we begin receiving XML for one of these record types, clear out the associated
544          * buffer so we don't accidentally carry over any data from a previous record.
545          */
546         if (!strcasecmp(el, "user"))                    memset(&usbuf, 0, sizeof (struct ctdluser));
547         else if (!strcasecmp(el, "openid"))             memset(openid_url, 0, sizeof openid_url);
548         else if (!strcasecmp(el, "room"))               memset(&qrbuf, 0, sizeof (struct ctdlroom));
549         else if (!strcasecmp(el, "room_messages"))      memset(FRname, 0, sizeof FRname);
550         else if (!strcasecmp(el, "floor"))              memset(&flbuf, 0, sizeof (struct floor));
551         else if (!strcasecmp(el, "visit"))              memset(&vbuf, 0, sizeof (visit));
552
553         else if (!strcasecmp(el, "message")) {
554                 memset(&smi, 0, sizeof (struct MetaData));
555                 import_msgnum = 0;
556         }
557         else if (!strcasecmp(el, "config")) {
558                 if (ikey != NULL) {
559                         free(ikey);
560                         ikey = NULL;
561                 }
562                 while (*attr) {
563                         if (!strcasecmp(attr[0], "key")) {
564                                 ikey = strdup(attr[1]);
565                         }
566                         attr += 2;
567                 }
568         }
569
570 }
571
572
573 int migr_userrecord(void *data, const char *el)
574 {
575         if (!strcasecmp(el, "u_version"))                       usbuf.version = atoi(ChrPtr(migr_chardata));
576         else if (!strcasecmp(el, "u_uid"))                      usbuf.uid = atol(ChrPtr(migr_chardata));
577         else if (!strcasecmp(el, "u_password"))                 safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
578         else if (!strcasecmp(el, "u_flags"))                    usbuf.flags = atoi(ChrPtr(migr_chardata));
579         else if (!strcasecmp(el, "u_timescalled"))              usbuf.timescalled = atol(ChrPtr(migr_chardata));
580         else if (!strcasecmp(el, "u_posted"))                   usbuf.posted = atol(ChrPtr(migr_chardata));
581         else if (!strcasecmp(el, "u_axlevel"))                  usbuf.axlevel = atoi(ChrPtr(migr_chardata));
582         else if (!strcasecmp(el, "u_usernum"))                  usbuf.usernum = atol(ChrPtr(migr_chardata));
583         else if (!strcasecmp(el, "u_lastcall"))                 usbuf.lastcall = atol(ChrPtr(migr_chardata));
584         else if (!strcasecmp(el, "u_USuserpurge"))              usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
585         else if (!strcasecmp(el, "u_fullname"))                 safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
586         else if (!strcasecmp(el, "u_msgnum_bio"))               usbuf.msgnum_bio = atol(ChrPtr(migr_chardata));
587         else if (!strcasecmp(el, "u_msgnum_pic"))               usbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
588         else return 0;
589         return 1;
590 }
591
592
593 int migr_roomrecord(void *data, const char *el)
594 {
595         if (!strcasecmp(el, "QRname"))                          safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
596         else if (!strcasecmp(el, "QRpasswd"))                   safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
597         else if (!strcasecmp(el, "QRroomaide"))                 qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
598         else if (!strcasecmp(el, "QRhighest"))                  qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
599         else if (!strcasecmp(el, "QRgen"))                      qrbuf.QRgen = atol(ChrPtr(migr_chardata));
600         else if (!strcasecmp(el, "QRflags"))                    qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
601         else if (!strcasecmp(el, "QRdirname"))                  safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
602         else if (!strcasecmp(el, "QRfloor"))                    qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
603         else if (!strcasecmp(el, "QRmtime"))                    qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
604         else if (!strcasecmp(el, "QRexpire_mode"))              qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
605         else if (!strcasecmp(el, "QRexpire_value"))             qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
606         else if (!strcasecmp(el, "QRnumber"))                   qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
607         else if (!strcasecmp(el, "QRorder"))                    qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
608         else if (!strcasecmp(el, "QRflags2"))                   qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
609         else if (!strcasecmp(el, "QRdefaultview"))              qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
610         else if (!strcasecmp(el, "msgnum_info"))                qrbuf.msgnum_info = atol(ChrPtr(migr_chardata));
611         else if (!strcasecmp(el, "msgnum_pic"))                 qrbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
612         else return 0;
613         return 1;
614 }
615
616 int migr_floorrecord(void *data, const char *el)
617 {
618         if (!strcasecmp(el, "f_num"))                   floornum = atoi(ChrPtr(migr_chardata));
619         else if (!strcasecmp(el, "f_flags"))                    flbuf.f_flags = atoi(ChrPtr(migr_chardata));
620         else if (!strcasecmp(el, "f_name"))                     safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
621         else if (!strcasecmp(el, "f_ref_count"))                flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
622         else if (!strcasecmp(el, "f_ep_expire_mode"))           flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
623         else if (!strcasecmp(el, "f_ep_expire_value"))          flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
624         else return 0;
625         return 1;
626 }
627
628 int migr_visitrecord(void *data, const char *el)
629 {
630         if (!strcasecmp(el, "v_roomnum"))                       vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
631         else if (!strcasecmp(el, "v_roomgen"))                  vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
632         else if (!strcasecmp(el, "v_usernum"))                  vbuf.v_usernum = atol(ChrPtr(migr_chardata));
633
634         else if (!strcasecmp(el, "v_seen")) {
635                 int is_textual_seen = 0;
636                 int i;
637                 int max = StrLength(migr_chardata);
638
639                 vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
640                 is_textual_seen = 0;
641                 for (i=0; i < max; ++i) 
642                         if (!isdigit(ChrPtr(migr_chardata)[i]))
643                                 is_textual_seen = 1;
644                 if (is_textual_seen)
645                         safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
646         }
647
648         else if (!strcasecmp(el, "v_answered"))                 safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
649         else if (!strcasecmp(el, "v_flags"))                    vbuf.v_flags = atoi(ChrPtr(migr_chardata));
650         else if (!strcasecmp(el, "v_view"))                     vbuf.v_view = atoi(ChrPtr(migr_chardata));
651         else return 0;
652         return 1;
653 }
654
655
656 void migr_xml_end(void *data, const char *el)
657 {
658         const char *ptr;
659         int msgcount = 0;
660         long msgnum = 0L;
661         long *msglist = NULL;
662         int msglist_alloc = 0;
663         /*** GENERAL STUFF ***/
664
665         if (!strcasecmp(el, "citadel_migrate_data")) {
666                 --citadel_migrate_data;
667                 return;
668         }
669
670         if (citadel_migrate_data != 1) {
671                 syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected.  Warning: ODD-DATA!", el);
672                 return;
673         }
674
675         // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
676
677         /*** CONFIG ***/
678
679         if (!strcasecmp(el, "config"))
680         {
681                 syslog(LOG_DEBUG, "Imported config key=%s", ikey);
682
683                 if (ikey != NULL) {
684                         CtdlSetConfigStr(ikey, (char *)ChrPtr(migr_chardata));
685                         free(ikey);
686                         ikey = NULL;
687                 }
688                 else {
689                         syslog(LOG_INFO, "Closed a <config> tag but no key name was supplied.");
690                 }
691         }
692
693         /*** USER ***/
694         else if ((!strncasecmp(el, HKEY("u_"))) && 
695                  migr_userrecord(data, el))
696                 ; /* Nothing to do anymore */
697         else if (!strcasecmp(el, "user")) {
698                 CtdlPutUser(&usbuf);
699                 syslog(LOG_INFO, "Imported user: %s", usbuf.fullname);
700         }
701
702         /*** OPENID ***/
703
704         else if (!strcasecmp(el, "oid_url"))                    safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
705         else if (!strcasecmp(el, "oid_usernum"))                openid_usernum = atol(ChrPtr(migr_chardata));
706
707         else if (!strcasecmp(el, "openid")) {                   /* see serv_openid_rp.c for a description of the record format */
708                 char *oid_data;
709                 int oid_data_len;
710                 oid_data_len = sizeof(long) + strlen(openid_url) + 1;
711                 oid_data = malloc(oid_data_len);
712                 memcpy(oid_data, &openid_usernum, sizeof(long));
713                 memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
714                 cdb_store(CDB_EXTAUTH, openid_url, strlen(openid_url), oid_data, oid_data_len);
715                 free(oid_data);
716                 syslog(LOG_INFO, "Imported OpenID: %s (%ld)", openid_url, openid_usernum);
717         }
718
719         /*** ROOM ***/
720         else if ((!strncasecmp(el, HKEY("QR"))) && 
721                  migr_roomrecord(data, el))
722                 ; /* Nothing to do anymore */
723         else if (!strcasecmp(el, "room")) {
724                 CtdlPutRoom(&qrbuf);
725                 syslog(LOG_INFO, "Imported room: %s", qrbuf.QRname);
726         }
727
728         /*** ROOM MESSAGE POINTERS ***/
729
730         else if (!strcasecmp(el, "FRname"))                     safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
731
732         else if (!strcasecmp(el, "FRmsglist")) {
733                 if (!IsEmptyStr(FRname)) {
734                         msgcount = 0;
735                         msglist_alloc = 1000;
736                         msglist = malloc(sizeof(long) * msglist_alloc);
737
738                         syslog(LOG_DEBUG, "Message list for: %s", FRname);
739
740                         ptr = ChrPtr(migr_chardata);
741                         while (*ptr != 0) {
742                                 while ((*ptr != 0) && (!isdigit(*ptr))) {
743                                         ++ptr;
744                                 }
745                                 if ((*ptr != 0) && (isdigit(*ptr))) {
746                                         msgnum = atol(ptr);
747                                         if (msgnum > 0L) {
748                                                 if (msgcount >= msglist_alloc) {
749                                                         msglist_alloc *= 2;
750                                                         msglist = realloc(msglist, sizeof(long) * msglist_alloc);
751                                                 }
752                                                 msglist[msgcount++] = msgnum;
753                                                 }
754                                         }
755                                         while ((*ptr != 0) && (isdigit(*ptr))) {
756                                                 ++ptr;
757                                         }
758                                 }
759                         }
760                         if (msgcount > 0) {
761                                 CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
762                         }
763                         free(msglist);
764                         msglist = NULL;
765                         msglist_alloc = 0;
766                         syslog(LOG_DEBUG, "Imported %d messages.", msgcount);
767                         if (server_shutting_down) {
768                                 return;
769                 }
770         }
771
772         /*** FLOORS ***/
773         else if ((!strncasecmp(el, HKEY("f_"))) && 
774                  migr_floorrecord(data, el))
775                 ; /* Nothing to do anymore */
776
777         else if (!strcasecmp(el, "floor")) {
778                 CtdlPutFloor(&flbuf, floornum);
779                 syslog(LOG_INFO, "Imported floor #%d (%s)", floornum, flbuf.f_name);
780         }
781
782         /*** VISITS ***/
783         else if ((!strncasecmp(el, HKEY("v_"))) && 
784                  migr_visitrecord(data, el))
785                 ; /* Nothing to do anymore */
786         else if (!strcasecmp(el, "visit")) {
787                 put_visit(&vbuf);
788                 syslog(LOG_INFO, "Imported visit: %ld/%ld/%ld", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
789         }
790
791         /*** MESSAGES ***/
792         
793         else if (!strcasecmp(el, "msg_msgnum"))                 smi.meta_msgnum = import_msgnum = atol(ChrPtr(migr_chardata));
794         else if (!strcasecmp(el, "msg_meta_refcount"))          smi.meta_refcount = atoi(ChrPtr(migr_chardata));
795         else if (!strcasecmp(el, "msg_meta_rfc822_length"))     smi.meta_rfc822_length = atoi(ChrPtr(migr_chardata));
796         else if (!strcasecmp(el, "msg_meta_content_type"))      safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
797
798         else if (!strcasecmp(el, "msg_text"))
799         {
800                 long rc;
801                 struct CtdlMessage *msg;
802
803                 FlushStrBuf(migr_MsgData);
804                 StrBufDecodeBase64To(migr_chardata, migr_MsgData);
805
806                 msg = CtdlDeserializeMessage(import_msgnum, -1,
807                                              ChrPtr(migr_MsgData), 
808                                              StrLength(migr_MsgData));
809                 if (msg != NULL) {
810                         rc = CtdlSaveThisMessage(msg, import_msgnum, 0);
811                         if (rc == 0) {
812                                 PutMetaData(&smi);
813                         }
814                         CM_Free(msg);
815                 }
816                 else {
817                         rc = -1;
818                 }
819
820                 syslog(LOG_INFO,
821                        "%s message #%ld, size=%d, refcount=%d, bodylength=%ld, content-type: %s",
822                        (rc!= 0)?"failed to import ":"Imported ",
823                        import_msgnum,
824                        StrLength(migr_MsgData),
825                        smi.meta_refcount,
826                        smi.meta_rfc822_length,
827                        smi.meta_content_type
828                 );
829                 memset(&smi, 0, sizeof(smi));
830         }
831
832         /*** MORE GENERAL STUFF ***/
833
834         FlushStrBuf(migr_chardata);
835 }
836
837
838
839
840 /*
841  * Import begins here
842  */
843 void migr_do_import(void) {
844         StrBuf *Buf;
845         XML_Parser xp;
846         int Finished = 0;
847         
848         unbuffer_output();
849         migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
850         migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
851         Buf = NewStrBufPlain(NULL, SIZ);
852         xp = XML_ParserCreate(NULL);
853         if (!xp) {
854                 cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
855                 return;
856         }
857         XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
858         XML_SetCharacterDataHandler(xp, migr_xml_chardata);
859
860         CC->dont_term = 1;
861
862         cprintf("%d sock it to me\n", SEND_LISTING);
863         unbuffer_output();
864
865         client_set_inbound_buf(SIZ * 10);
866
867         while (!Finished && client_read_random_blob(Buf, -1) >= 0) {
868                 if ((StrLength(Buf) > 4) &&
869                     !strcmp(ChrPtr(Buf) + StrLength(Buf) - 4, "000\n"))
870                 {
871                         Finished = 1;
872                         StrBufCutAt(Buf, StrLength(Buf) - 4, NULL);
873                 }
874                 if (server_shutting_down)
875                         break;  // Should we break or return?
876                 
877                 if (StrLength(Buf) == 0)
878                         continue;
879
880                 XML_Parse(xp, ChrPtr(Buf), StrLength(Buf), 0);
881                 FlushStrBuf(Buf);
882         }
883
884         XML_Parse(xp, "", 0, 1);
885         XML_ParserFree(xp);
886         FreeStrBuf(&Buf);
887         FreeStrBuf(&migr_chardata);
888         FreeStrBuf(&migr_MsgData);
889         rebuild_euid_index();
890         rebuild_usersbynumber();
891         CtdlSetConfigInt("MM_fulltext_wordbreaker", -1);        // Set an invalid wordbreaker to force re-indexing
892         CC->dont_term = 0;
893 }
894
895
896 /******************************************************************************
897  *                         Dispatcher, Common code                            *
898  ******************************************************************************/
899 /*
900  * Dump out the pathnames of directories which can be copied "as is"
901  */
902 void migr_do_listdirs(void) {
903         cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
904         cprintf("files|%s\n",           ctdl_file_dir);
905         cprintf("messages|%s\n",        ctdl_message_dir);
906         cprintf("keys|%s\n",            ctdl_key_dir);
907         cprintf("000\n");
908 }
909
910
911 /******************************************************************************
912  *                    Repair database integrity                               *
913  ******************************************************************************/
914
915 StrBuf *PlainMessageBuf = NULL;
916 HashList *UsedMessageIDS = NULL;
917
918 int migr_restore_message_metadata(long msgnum, int refcount)
919 {
920         CitContext *CCC = MyContext();
921         struct MetaData smi;
922         struct CtdlMessage *msg;
923         char *mptr = NULL;
924
925         /* We can use a static buffer here because there will never be more than
926          * one of this operation happening at any given time, and it's really best
927          * to just keep it allocated once instead of torturing malloc/free.
928          * Call this function with msgnum "-1" to free the buffer when finished.
929          */
930         static int encoded_alloc = 0;
931         static char *encoded_msg = NULL;
932
933         if (msgnum < 0) {
934                 if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
935                         free(encoded_msg);
936                         encoded_alloc = 0;
937                         encoded_msg = NULL;
938                         // todo FreeStrBuf(&PlainMessageBuf); PlainMessageBuf = NULL;
939                 }
940                 return 0;
941         }
942
943         if (PlainMessageBuf == NULL) {
944                 PlainMessageBuf = NewStrBufPlain(NULL, 10*SIZ);
945         }
946
947         /* Ok, here we go ... */
948
949         msg = CtdlFetchMessage(msgnum, 1, 0);
950         if (msg == NULL) {
951                 return 1;
952         }
953
954         GetMetaData(&smi, msgnum);
955         smi.meta_msgnum = msgnum;
956         smi.meta_refcount = refcount;
957         
958         /* restore the content type from the message body: */
959         mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
960         if (mptr != NULL) {
961                 char *aptr;
962                 safestrncpy(smi.meta_content_type, &mptr[13], sizeof smi.meta_content_type);
963                 striplt(smi.meta_content_type);
964                 aptr = smi.meta_content_type;
965                 while (!IsEmptyStr(aptr)) {
966                         if ((*aptr == ';')
967                             || (*aptr == ' ')
968                             || (*aptr == 13)
969                             || (*aptr == 10)) {
970                                 memset(aptr, 0, sizeof(smi.meta_content_type) - (aptr - smi.meta_content_type));
971                         }
972                         else aptr++;
973                 }
974         }
975
976         CCC->redirect_buffer = PlainMessageBuf;
977         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
978         smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
979         CCC->redirect_buffer = NULL;
980
981
982         syslog(LOG_INFO,
983                "Setting message #%ld meta data to: refcount=%d, bodylength=%ld, content-type: %s",
984                smi.meta_msgnum,
985                smi.meta_refcount,
986                smi.meta_rfc822_length,
987                smi.meta_content_type
988         );
989
990         PutMetaData(&smi);
991
992         CM_Free(msg);
993
994         return 0;
995 }
996
997 void migr_check_room_msg(long msgnum, void *userdata) {
998         fprintf(migr_global_message_list, "%ld %s\n", msgnum, CC->room.QRname);
999 }
1000
1001
1002 void migr_check_rooms_backend(struct ctdlroom *buf, void *data) {
1003
1004         /* message list goes inside this tag */
1005
1006         CtdlGetRoom(&CC->room, buf->QRname);
1007         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_check_room_msg, NULL);
1008 }
1009
1010 void RemoveMessagesFromRooms(StrBuf *RoomNameVec, long msgnum) {
1011         struct MetaData smi;
1012         const char *Pos = NULL;
1013         StrBuf *oneRoom = NewStrBuf();
1014
1015         syslog(LOG_INFO, "removing message pointer %ld from these rooms: %s", msgnum, ChrPtr(RoomNameVec));
1016
1017         while (Pos != StrBufNOTNULL){
1018                 StrBufExtract_NextToken(oneRoom, RoomNameVec, &Pos, '|');
1019                 CtdlDeleteMessages(ChrPtr(oneRoom), &msgnum, 1, "");
1020         };
1021         GetMetaData(&smi, msgnum);
1022         TDAP_AdjRefCount(msgnum, -smi.meta_refcount);
1023 }
1024
1025 void migr_do_restore_meta(void) {
1026         char buf[SIZ];
1027         int failGetMessage;
1028         long msgnum = 0;
1029         int lastnum = 0;
1030         int refcount = 0;
1031         CitContext *Ctx;
1032         char *prn;
1033         StrBuf *RoomNames;
1034         char cmd[SIZ];
1035
1036         migr_global_message_list = fopen(migr_tempfilename1, "w");
1037         if (migr_global_message_list != NULL) {
1038                 CtdlForEachRoom(migr_check_rooms_backend, NULL);
1039                 fclose(migr_global_message_list);
1040         }
1041
1042         /*
1043          * Process the 'global' message list.  (Sort it and remove dups.
1044          * Dups are ok because a message may be in more than one room, but
1045          * this will be handled by exporting the reference count, not by
1046          * exporting the message multiple times.)
1047          */
1048         snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
1049         if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d", errno);
1050
1051         RoomNames = NewStrBuf();
1052         Ctx = CC;
1053         migr_global_message_list = fopen(migr_tempfilename2, "r");
1054         if (migr_global_message_list != NULL) {
1055                 syslog(LOG_INFO, "Opened %s", migr_tempfilename1);
1056                 while ((Ctx->kill_me == 0) && 
1057                        (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
1058                         msgnum = atol(buf);
1059                         if (msgnum == 0L) 
1060                                 continue;
1061                         if (lastnum == 0) {
1062                                 lastnum = msgnum;
1063                         }
1064                         prn = strchr(buf, ' ');
1065                         if (lastnum != msgnum) {
1066                                 failGetMessage = migr_restore_message_metadata(lastnum, refcount);
1067                                 if (failGetMessage) {
1068                                         RemoveMessagesFromRooms(RoomNames, lastnum);
1069                                 }
1070                                 refcount = 1;
1071                                 lastnum = msgnum;
1072                                 if (prn != NULL)
1073                                         StrBufPlain(RoomNames, prn + 1, -1);
1074                                 StrBufTrim(RoomNames);
1075                         }
1076                         else {
1077                                 if (prn != NULL) {
1078                                         if (StrLength(RoomNames) > 0)
1079                                                 StrBufAppendBufPlain(RoomNames, HKEY("|"), 0);
1080                                         StrBufAppendBufPlain(RoomNames, prn, -1, 1);
1081                                         StrBufTrim(RoomNames);
1082                                 }
1083                                 refcount ++;
1084                         }
1085                         lastnum = msgnum;
1086                 }
1087                 failGetMessage = migr_restore_message_metadata(msgnum, refcount);
1088                 if (failGetMessage) {
1089                         RemoveMessagesFromRooms(RoomNames, lastnum);
1090                 }
1091                 fclose(migr_global_message_list);
1092         }
1093
1094         migr_restore_message_metadata(-1L, -1); /* This frees the encoding buffer */
1095         cprintf("%d system analysis completed", CIT_OK);
1096         Ctx->kill_me = 1;
1097 }
1098
1099
1100
1101
1102 /******************************************************************************
1103  *                         Dispatcher, Common code                            *
1104  ******************************************************************************/
1105 void cmd_migr(char *cmdbuf) {
1106         char cmd[32];
1107         
1108         if (CtdlAccessCheck(ac_internal)) return;
1109         
1110         if (CtdlTrySingleUser())
1111         {
1112                 CtdlDisableHouseKeeping();
1113                 CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
1114                 CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
1115
1116                 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
1117                 if (!strcasecmp(cmd, "export")) {
1118                         migr_do_export();
1119                 }
1120                 else if (!strcasecmp(cmd, "import")) {
1121                         migr_do_import();
1122                 }
1123                 else if (!strcasecmp(cmd, "listdirs")) {
1124                         migr_do_listdirs();
1125                 }
1126                 else if (!strcasecmp(cmd, "restoremeta")) {
1127                         migr_do_restore_meta();
1128                 }
1129                 else {
1130                         cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
1131                 }
1132
1133                 unlink(migr_tempfilename1);
1134                 unlink(migr_tempfilename2);
1135
1136                 CtdlEnableHouseKeeping();
1137                 CtdlEndSingleUser();
1138         }
1139         else
1140         {
1141                 cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
1142         }
1143 }
1144
1145 /******************************************************************************
1146  *                              Module Hook                                  *
1147  ******************************************************************************/
1148
1149 CTDL_MODULE_INIT(migrate)
1150 {
1151         if (!threading)
1152         {
1153                 CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
1154         }
1155         
1156         /* return our module name for the log */
1157         return "migrate";
1158 }