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