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