3f9ee3a495848d50afb128c51eac1f7b8be7fe12
[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-2015 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 /*
16  * Explanation of <progress> tags:
17  *
18  * 0%              nothing
19  * 2%              finished exporting configuration
20  * 7%              finished exporting users
21  * 12%             finished exporting openids
22  * 17%             finished exporting rooms
23  * 18%             finished exporting floors
24  * 25%             finished exporting visits
25  * 100%            finished exporting messages
26  */
27
28 #include "sysdep.h"
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <stdio.h>
32 #include <fcntl.h>
33 #include <signal.h>
34 #include <pwd.h>
35 #include <errno.h>
36 #include <sys/types.h>
37
38 #if TIME_WITH_SYS_TIME
39 # include <sys/time.h>
40 # include <time.h>
41 #else
42 # if HAVE_SYS_TIME_H
43 #  include <sys/time.h>
44 # else
45 #  include <time.h>
46 # endif
47 #endif
48
49 #include <sys/wait.h>
50 #include <string.h>
51 #include <ctype.h>
52 #include <limits.h>
53 #include <expat.h>
54 #include <libcitadel.h>
55 #include "citadel.h"
56 #include "server.h"
57 #include "citserver.h"
58 #include "support.h"
59 #include "config.h"
60 #include "database.h"
61 #include "msgbase.h"
62 #include "user_ops.h"
63 #include "euidindex.h"
64 #include "ctdl_module.h"
65
66 #define END_OF_MESSAGE  "---eom---dbd---"
67
68 char migr_tempfilename1[PATH_MAX];
69 char migr_tempfilename2[PATH_MAX];
70 FILE *migr_global_message_list;
71 int total_msgs = 0;
72
73 /*
74  * Code which implements the export appears in this section
75  */
76
77 /*
78  * Output a string to the client with these characters escaped:  & < >
79  */
80 void xml_strout(char *str) {
81
82         char *c = str;
83
84         if (str == NULL) {
85                 return;
86         }
87
88         while (*c != 0) {
89                 if (*c == '\"') {
90                         client_write("&quot;", 6);
91                 }
92                 else if (*c == '\'') {
93                         client_write("&apos;", 6);
94                 }
95                 else if (*c == '<') {
96                         client_write("&lt;", 4);
97                 }
98                 else if (*c == '>') {
99                         client_write("&gt;", 4);
100                 }
101                 else if (*c == '&') {
102                         client_write("&amp;", 5);
103                 }
104                 else {
105                         client_write(c, 1);
106                 }
107                 ++c;
108         }
109 }
110
111
112 /*
113  * Export a user record as XML
114  */
115 void migr_export_users_backend(struct ctdluser *buf, void *data) {
116         client_write("<user>\n", 7);
117         cprintf("<u_version>%d</u_version>\n", buf->version);
118         cprintf("<u_uid>%ld</u_uid>\n", (long)buf->uid);
119         client_write("<u_password>", 12);       xml_strout(buf->password);              client_write("</u_password>\n", 14);
120         cprintf("<u_flags>%u</u_flags>\n", buf->flags);
121         cprintf("<u_timescalled>%ld</u_timescalled>\n", buf->timescalled);
122         cprintf("<u_posted>%ld</u_posted>\n", buf->posted);
123         cprintf("<u_axlevel>%d</u_axlevel>\n", buf->axlevel);
124         cprintf("<u_usernum>%ld</u_usernum>\n", buf->usernum);
125         cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)buf->lastcall);
126         cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", buf->USuserpurge);
127         client_write("<u_fullname>", 12);       xml_strout(buf->fullname);              client_write("</u_fullname>\n", 14);
128         client_write("</user>\n", 8);
129 }
130
131
132 void migr_export_users(void) {
133         ForEachUser(migr_export_users_backend, NULL);
134 }
135
136
137 void migr_export_room_msg(long msgnum, void *userdata) {
138         cprintf("%ld\n", msgnum);
139         fprintf(migr_global_message_list, "%ld\n", msgnum);
140 }
141
142
143 void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
144         client_write("<room>\n", 7);
145         client_write("<QRname>", 8);    xml_strout(buf->QRname);        client_write("</QRname>\n", 10);
146         client_write("<QRpasswd>", 10); xml_strout(buf->QRpasswd);      client_write("</QRpasswd>\n", 12);
147         cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
148         cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
149         cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
150         cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
151         if (buf->QRflags & QR_DIRECTORY) {
152                 client_write("<QRdirname>", 11);
153                 xml_strout(buf->QRdirname);
154                 client_write("</QRdirname>\n", 13);
155         }
156         cprintf("<QRinfo>%ld</QRinfo>\n", buf->QRinfo);
157         cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
158         cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
159         cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
160         cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
161         cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
162         cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
163         cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
164         cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
165         client_write("</room>\n", 8);
166
167         /* message list goes inside this tag */
168
169         CtdlGetRoom(&CC->room, buf->QRname);
170         client_write("<room_messages>", 15);
171         client_write("<FRname>", 8);    xml_strout(CC->room.QRname);    client_write("</FRname>\n", 10);
172         client_write("<FRmsglist>", 11);
173         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
174         client_write("</FRmsglist>\n", 13);
175         client_write("</room_messages>\n", 17);
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\n", errno);
197         snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
198         if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", 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("<floor>\n", 8);
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("<f_name>", 8); xml_strout(buf->f_name); client_write("</f_name>\n", 10);
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("</floor>\n", 9);
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 /* 
253  *  Traverse the visits file...
254  */
255 void migr_export_visits(void) {
256         visit vbuf;
257         struct cdbdata *cdbv;
258
259         cdb_rewind(CDB_VISIT);
260
261         while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
262                 memset(&vbuf, 0, sizeof(visit));
263                 memcpy(&vbuf, cdbv->ptr,
264                        ((cdbv->len > sizeof(visit)) ?
265                         sizeof(visit) : cdbv->len));
266                 cdb_free(cdbv);
267
268                 client_write("<visit>\n", 8);
269                 cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
270                 cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
271                 cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
272
273                 client_write("<v_seen>", 8);
274                 if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
275                         xml_strout(vbuf.v_seen);
276                 }
277                 else {
278                         cprintf("%ld", vbuf.v_lastseen);
279                 }
280                 client_write("</v_seen>", 9);
281
282                 if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
283                         client_write("<v_answered>", 12);
284                         xml_strout(vbuf.v_answered);
285                         client_write("</v_answered>\n", 14);
286                 }
287
288                 cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
289                 cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
290                 client_write("</visit>\n", 9);
291         }
292 }
293
294
295 void migr_export_message(long msgnum) {
296         struct MetaData smi;
297         struct CtdlMessage *msg;
298         struct ser_ret smr;
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("<message>\n", 10);
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         client_write("<msg_meta_content_type>", 23); xml_strout(smi.meta_content_type); client_write("</msg_meta_content_type>\n", 25);
327
328         client_write("<msg_text>", 10);
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("</msg_text>\n", 12);
352         client_write("</message>\n", 11);
353 }
354
355
356
357 void migr_export_openids(void) {
358         struct cdbdata *cdboi;
359         long usernum;
360         char url[512];
361
362         cdb_rewind(CDB_OPENID);
363         while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
364                 if (cdboi->len > sizeof(long)) {
365                         client_write("<openid>\n", 9);
366                         memcpy(&usernum, cdboi->ptr, sizeof(long));
367                         snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
368                         client_write("<oid_url>", 9);
369                         xml_strout(url);
370                         client_write("</oid_url>\n", 11);
371                         cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
372                         client_write("</openid>\n", 10);
373                 }
374                 cdb_free(cdboi);
375         }
376 }
377
378
379 void migr_export_configs(void) {
380         struct cdbdata *cdbcfg;
381         int keylen = 0;
382         char *key = NULL;
383         char *value = NULL;
384
385         cdb_rewind(CDB_CONFIG);
386         while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
387
388                 keylen = strlen(cdbcfg->ptr);
389                 key = cdbcfg->ptr;
390                 value = cdbcfg->ptr + keylen + 1;
391
392                 client_write("<config key=\"", 13);
393                 xml_strout(key);
394                 client_write("\">", 2);
395                 xml_strout(value);
396                 client_write("</config>\n", 10);
397                 cdb_free(cdbcfg);
398         }
399 }
400
401
402
403
404 void migr_export_messages(void) {
405         char buf[SIZ];
406         long msgnum;
407         int count = 0;
408         int progress = 0;
409         int prev_progress = 0;
410         CitContext *Ctx;
411
412         Ctx = CC;
413         migr_global_message_list = fopen(migr_tempfilename1, "r");
414         if (migr_global_message_list != NULL) {
415                 syslog(LOG_INFO, "Opened %s\n", migr_tempfilename1);
416                 while ((Ctx->kill_me == 0) && 
417                        (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
418                         msgnum = atol(buf);
419                         if (msgnum > 0L) {
420                                 migr_export_message(msgnum);
421                                 ++count;
422                         }
423                         progress = (count * 74 / total_msgs) + 25 ;
424                         if ((progress > prev_progress) && (progress < 100)) {
425                                 cprintf("<progress>%d</progress>\n", progress);
426                         }
427                         prev_progress = progress;
428                 }
429                 fclose(migr_global_message_list);
430         }
431         if (Ctx->kill_me == 0)
432                 syslog(LOG_INFO, "Exported %d messages.\n", count);
433         else
434                 syslog(LOG_ERR, "Export aborted due to client disconnect! \n");
435
436         migr_export_message(-1L);       /* This frees the encoding buffer */
437 }
438
439
440
441 void migr_do_export(void) {
442         CitContext *Ctx;
443
444         Ctx = CC;
445         cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
446         Ctx->dont_term = 1;
447
448         client_write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", 40);
449         client_write("<citadel_migrate_data>\n", 23);
450         cprintf("<version>%d</version>\n", REV_LEVEL);
451         cprintf("<progress>%d</progress>\n", 0);
452
453         /* export the configuration database */
454         migr_export_configs();
455         cprintf("<progress>%d</progress>\n", 2);
456         
457         if (Ctx->kill_me == 0)  migr_export_users();
458         cprintf("<progress>%d</progress>\n", 7);
459         if (Ctx->kill_me == 0)  migr_export_openids();
460         cprintf("<progress>%d</progress>\n", 12);
461         if (Ctx->kill_me == 0)  migr_export_rooms();
462         cprintf("<progress>%d</progress>\n", 17);
463         if (Ctx->kill_me == 0)  migr_export_floors();
464         cprintf("<progress>%d</progress>\n", 18);
465         if (Ctx->kill_me == 0)  migr_export_visits();
466         cprintf("<progress>%d</progress>\n", 25);
467         if (Ctx->kill_me == 0)  migr_export_messages();
468         client_write("</citadel_migrate_data>\n", 24);
469         cprintf("<progress>%d</progress>\n", 100);
470         client_write("000\n", 4);
471         Ctx->dont_term = 0;
472 }
473
474
475
476
477         
478 /*
479  * Here's the code that implements the import side.  It's going to end up being
480  * one big loop with lots of global variables.  I don't care.  You wouldn't run
481  * multiple concurrent imports anyway.  If this offends your delicate sensibilities
482  * then go rewrite it in Ruby on Rails or something.
483  */
484
485
486 int citadel_migrate_data = 0;           /* Are we inside a <citadel_migrate_data> tag pair? */
487 StrBuf *migr_chardata = NULL;
488 StrBuf *migr_MsgData = NULL;
489 struct ctdluser usbuf;
490 struct ctdlroom qrbuf;
491 char openid_url[512];
492 long openid_usernum = 0;
493 char FRname[ROOMNAMELEN];
494 struct floor flbuf;
495 int floornum = 0;
496 visit vbuf;
497 struct MetaData smi;
498 long import_msgnum = 0;
499
500 /*
501  * This callback stores up the data which appears in between tags.
502  */
503 void migr_xml_chardata(void *data, const XML_Char *s, int len)
504 {
505         StrBufAppendBufPlain(migr_chardata, s, len, 0);
506 }
507
508
509 void migr_xml_start(void *data, const char *el, const char **attr) {
510         int i;
511
512         /*** GENERAL STUFF ***/
513
514         /* Reset chardata_len to zero and init buffer */
515         FlushStrBuf(migr_chardata);
516         FlushStrBuf(migr_MsgData);
517
518         if (!strcasecmp(el, "citadel_migrate_data")) {
519                 ++citadel_migrate_data;
520
521                 /* As soon as it looks like the input data is a genuine Citadel XML export,
522                  * whack the existing database on disk to make room for the new one.
523                  */
524                 if (citadel_migrate_data == 1) {
525                         for (i = 0; i < MAXCDB; ++i) {
526                                 cdb_trunc(i);
527                         }
528                 }
529                 return;
530         }
531
532         if (citadel_migrate_data != 1) {
533                 syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected.  Warning: ODD-DATA!\n", el);
534                 return;
535         }
536
537         /* When we begin receiving XML for one of these record types, clear out the associated
538          * buffer so we don't accidentally carry over any data from a previous record.
539          */
540         if (!strcasecmp(el, "user"))                    memset(&usbuf, 0, sizeof (struct ctdluser));
541         else if (!strcasecmp(el, "openid"))             memset(openid_url, 0, sizeof openid_url);
542         else if (!strcasecmp(el, "room"))               memset(&qrbuf, 0, sizeof (struct ctdlroom));
543         else if (!strcasecmp(el, "room_messages"))      memset(FRname, 0, sizeof FRname);
544         else if (!strcasecmp(el, "floor"))              memset(&flbuf, 0, sizeof (struct floor));
545         else if (!strcasecmp(el, "visit"))              memset(&vbuf, 0, sizeof (visit));
546
547         else if (!strcasecmp(el, "message")) {
548                 memset(&smi, 0, sizeof (struct MetaData));
549                 import_msgnum = 0;
550         }
551         else if (!strcasecmp(el, "config")) {
552                 syslog(LOG_DEBUG, "\033[31m IMPORT OF CONFIG START ELEMENT FIXME\033\0m");
553         }
554
555 }
556
557
558 int migr_userrecord(void *data, const char *el)
559 {
560         if (!strcasecmp(el, "u_version"))                       usbuf.version = atoi(ChrPtr(migr_chardata));
561         else if (!strcasecmp(el, "u_uid"))                      usbuf.uid = atol(ChrPtr(migr_chardata));
562         else if (!strcasecmp(el, "u_password"))                 safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
563         else if (!strcasecmp(el, "u_flags"))                    usbuf.flags = atoi(ChrPtr(migr_chardata));
564         else if (!strcasecmp(el, "u_timescalled"))              usbuf.timescalled = atol(ChrPtr(migr_chardata));
565         else if (!strcasecmp(el, "u_posted"))                   usbuf.posted = atol(ChrPtr(migr_chardata));
566         else if (!strcasecmp(el, "u_axlevel"))                  usbuf.axlevel = atoi(ChrPtr(migr_chardata));
567         else if (!strcasecmp(el, "u_usernum"))                  usbuf.usernum = atol(ChrPtr(migr_chardata));
568         else if (!strcasecmp(el, "u_lastcall"))                 usbuf.lastcall = atol(ChrPtr(migr_chardata));
569         else if (!strcasecmp(el, "u_USuserpurge"))              usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
570         else if (!strcasecmp(el, "u_fullname"))                 safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
571         else return 0;
572         return 1;
573 }
574
575 int migr_roomrecord(void *data, const char *el)
576 {
577         if (!strcasecmp(el, "QRname"))                  safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
578         else if (!strcasecmp(el, "QRpasswd"))                   safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
579         else if (!strcasecmp(el, "QRroomaide"))                 qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
580         else if (!strcasecmp(el, "QRhighest"))                  qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
581         else if (!strcasecmp(el, "QRgen"))                      qrbuf.QRgen = atol(ChrPtr(migr_chardata));
582         else if (!strcasecmp(el, "QRflags"))                    qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
583         else if (!strcasecmp(el, "QRdirname"))                  safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
584         else if (!strcasecmp(el, "QRinfo"))                     qrbuf.QRinfo = atol(ChrPtr(migr_chardata));
585         else if (!strcasecmp(el, "QRfloor"))                    qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
586         else if (!strcasecmp(el, "QRmtime"))                    qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
587         else if (!strcasecmp(el, "QRexpire_mode"))              qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
588         else if (!strcasecmp(el, "QRexpire_value"))             qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
589         else if (!strcasecmp(el, "QRnumber"))                   qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
590         else if (!strcasecmp(el, "QRorder"))                    qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
591         else if (!strcasecmp(el, "QRflags2"))                   qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
592         else if (!strcasecmp(el, "QRdefaultview"))              qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
593         else return 0;
594         return 1;
595 }
596
597 int migr_floorrecord(void *data, const char *el)
598 {
599         if (!strcasecmp(el, "f_num"))                   floornum = atoi(ChrPtr(migr_chardata));
600         else if (!strcasecmp(el, "f_flags"))                    flbuf.f_flags = atoi(ChrPtr(migr_chardata));
601         else if (!strcasecmp(el, "f_name"))                     safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
602         else if (!strcasecmp(el, "f_ref_count"))                flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
603         else if (!strcasecmp(el, "f_ep_expire_mode"))           flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
604         else if (!strcasecmp(el, "f_ep_expire_value"))          flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
605         else return 0;
606         return 1;
607 }
608
609 int migr_visitrecord(void *data, const char *el)
610 {
611         if (!strcasecmp(el, "v_roomnum"))                       vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
612         else if (!strcasecmp(el, "v_roomgen"))                  vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
613         else if (!strcasecmp(el, "v_usernum"))                  vbuf.v_usernum = atol(ChrPtr(migr_chardata));
614
615         else if (!strcasecmp(el, "v_seen")) {
616                 int is_textual_seen = 0;
617                 int i;
618                 int max = StrLength(migr_chardata);
619
620                 vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
621                 is_textual_seen = 0;
622                 for (i=0; i < max; ++i) 
623                         if (!isdigit(ChrPtr(migr_chardata)[i]))
624                                 is_textual_seen = 1;
625                 if (is_textual_seen)
626                         safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
627         }
628
629         else if (!strcasecmp(el, "v_answered"))                 safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
630         else if (!strcasecmp(el, "v_flags"))                    vbuf.v_flags = atoi(ChrPtr(migr_chardata));
631         else if (!strcasecmp(el, "v_view"))                     vbuf.v_view = atoi(ChrPtr(migr_chardata));
632         else return 0;
633         return 1;
634 }
635
636
637 void migr_xml_end(void *data, const char *el)
638 {
639         const char *ptr;
640         int msgcount = 0;
641         long msgnum = 0L;
642         long *msglist = NULL;
643         int msglist_alloc = 0;
644         /*** GENERAL STUFF ***/
645
646         if (!strcasecmp(el, "citadel_migrate_data")) {
647                 --citadel_migrate_data;
648                 return;
649         }
650
651         if (citadel_migrate_data != 1) {
652                 syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected.  Warning: ODD-DATA!\n", el);
653                 return;
654         }
655
656         // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
657
658         /*** CONFIG ***/
659
660         if (!strcasecmp(el, "config"))
661         {
662                 syslog(LOG_DEBUG, "\033[31m IMPORT OF CONFIG END ELEMENT FIXME\033\0m");
663                 CtdlSetConfigInt("c_enable_fulltext", 0);       /* always disable FIXME put this somewhere more appropriate */
664         }
665
666         /*** USER ***/
667         else if ((!strncasecmp(el, HKEY("u_"))) && 
668                  migr_userrecord(data, el))
669                 ; /* Nothing to do anymore */
670         else if (!strcasecmp(el, "user")) {
671                 CtdlPutUser(&usbuf);
672                 syslog(LOG_INFO, "Imported user: %s\n", usbuf.fullname);
673         }
674
675         /*** OPENID ***/
676
677         else if (!strcasecmp(el, "oid_url"))                    safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
678         else if (!strcasecmp(el, "oid_usernum"))                openid_usernum = atol(ChrPtr(migr_chardata));
679
680         else if (!strcasecmp(el, "openid")) {                   /* see serv_openid_rp.c for a description of the record format */
681                 char *oid_data;
682                 int oid_data_len;
683                 oid_data_len = sizeof(long) + strlen(openid_url) + 1;
684                 oid_data = malloc(oid_data_len);
685                 memcpy(oid_data, &openid_usernum, sizeof(long));
686                 memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
687                 cdb_store(CDB_OPENID, openid_url, strlen(openid_url), oid_data, oid_data_len);
688                 free(oid_data);
689                 syslog(LOG_INFO, "Imported OpenID: %s (%ld)\n", openid_url, openid_usernum);
690         }
691
692         /*** ROOM ***/
693         else if ((!strncasecmp(el, HKEY("QR"))) && 
694                  migr_roomrecord(data, el))
695                 ; /* Nothing to do anymore */
696         else if (!strcasecmp(el, "room")) {
697                 CtdlPutRoom(&qrbuf);
698                 syslog(LOG_INFO, "Imported room: %s\n", qrbuf.QRname);
699         }
700
701         /*** ROOM MESSAGE POINTERS ***/
702
703         else if (!strcasecmp(el, "FRname"))                     safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
704
705         else if (!strcasecmp(el, "FRmsglist")) {
706                 if (!IsEmptyStr(FRname)) {
707                         msgcount = 0;
708                         msglist_alloc = 1000;
709                         msglist = malloc(sizeof(long) * msglist_alloc);
710
711                         syslog(LOG_DEBUG, "Message list for: %s\n", FRname);
712
713                         ptr = ChrPtr(migr_chardata);
714                         while (*ptr != 0) {
715                                 while ((*ptr != 0) && (!isdigit(*ptr))) {
716                                         ++ptr;
717                                 }
718                                 if ((*ptr != 0) && (isdigit(*ptr))) {
719                                         msgnum = atol(ptr);
720                                         if (msgnum > 0L) {
721                                                 if (msgcount >= msglist_alloc) {
722                                                         msglist_alloc *= 2;
723                                                         msglist = realloc(msglist, sizeof(long) * msglist_alloc);
724                                                 }
725                                                 msglist[msgcount++] = msgnum;
726                                                 }
727                                         }
728                                         while ((*ptr != 0) && (isdigit(*ptr))) {
729                                                 ++ptr;
730                                         }
731                                 }
732                         }
733                         if (msgcount > 0) {
734                                 CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
735                         }
736                         free(msglist);
737                         msglist = NULL;
738                         msglist_alloc = 0;
739                         syslog(LOG_DEBUG, "Imported %d messages.\n", msgcount);
740                         if (server_shutting_down) {
741                                 return;
742                 }
743         }
744
745         /*** FLOORS ***/
746         else if ((!strncasecmp(el, HKEY("f_"))) && 
747                  migr_floorrecord(data, el))
748                 ; /* Nothing to do anymore */
749
750         else if (!strcasecmp(el, "floor")) {
751                 CtdlPutFloor(&flbuf, floornum);
752                 syslog(LOG_INFO, "Imported floor #%d (%s)\n", floornum, flbuf.f_name);
753         }
754
755         /*** VISITS ***/
756         else if ((!strncasecmp(el, HKEY("v_"))) && 
757                  migr_visitrecord(data, el))
758                 ; /* Nothing to do anymore */
759         else if (!strcasecmp(el, "visit")) {
760                 put_visit(&vbuf);
761                 syslog(LOG_INFO, "Imported visit: %ld/%ld/%ld\n", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
762         }
763
764         /*** MESSAGES ***/
765         
766         else if (!strcasecmp(el, "msg_msgnum"))                 import_msgnum = atol(ChrPtr(migr_chardata));
767         else if (!strcasecmp(el, "msg_meta_refcount"))          smi.meta_refcount = atoi(ChrPtr(migr_chardata));
768         else if (!strcasecmp(el, "msg_meta_content_type"))      safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
769
770         else if (!strcasecmp(el, "msg_text"))
771         {
772
773                 FlushStrBuf(migr_MsgData);
774                 StrBufDecodeBase64To(migr_MsgData, migr_MsgData);
775
776                 cdb_store(CDB_MSGMAIN,
777                           &import_msgnum,
778                           sizeof(long),
779                           ChrPtr(migr_MsgData), 
780                           StrLength(migr_MsgData) + 1);
781
782                 smi.meta_msgnum = import_msgnum;
783                 PutMetaData(&smi);
784
785                 syslog(LOG_INFO,
786                        "Imported message #%ld, size=%d, refcount=%d, content-type: %s\n",
787                        import_msgnum,
788                        StrLength(migr_MsgData),
789                        smi.meta_refcount,
790                        smi.meta_content_type);
791         }
792
793         /*** MORE GENERAL STUFF ***/
794
795         FlushStrBuf(migr_chardata);
796 }
797
798
799
800
801 /*
802  * Import begins here
803  */
804 void migr_do_import(void) {
805         StrBuf *Buf;
806         XML_Parser xp;
807         int Finished = 0;
808         
809         unbuffer_output();
810         migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
811         migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
812         Buf = NewStrBufPlain(NULL, SIZ);
813         xp = XML_ParserCreate(NULL);
814         if (!xp) {
815                 cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
816                 return;
817         }
818         XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
819         XML_SetCharacterDataHandler(xp, migr_xml_chardata);
820
821         CC->dont_term = 1;
822
823         cprintf("%d sock it to me\n", SEND_LISTING);
824         unbuffer_output();
825
826         client_set_inbound_buf(SIZ * 10);
827
828         while (!Finished && client_read_random_blob(Buf, -1) >= 0) {
829                 if ((StrLength(Buf) > 4) &&
830                     !strcmp(ChrPtr(Buf) + StrLength(Buf) - 4, "000\n"))
831                 {
832                         Finished = 1;
833                         StrBufCutAt(Buf, StrLength(Buf) - 4, NULL);
834                 }
835                 if (server_shutting_down)
836                         break;  // Should we break or return?
837                 
838                 if (StrLength(Buf) == 0)
839                         continue;
840
841                 XML_Parse(xp, ChrPtr(Buf), StrLength(Buf), 0);
842                 FlushStrBuf(Buf);
843         }
844
845         XML_Parse(xp, "", 0, 1);
846         XML_ParserFree(xp);
847         FreeStrBuf(&Buf);
848         FreeStrBuf(&migr_chardata);
849         FreeStrBuf(&migr_MsgData);
850         rebuild_euid_index();
851         rebuild_usersbynumber();
852         CC->dont_term = 0;
853 }
854
855
856
857 /*
858  * Dump out the pathnames of directories which can be copied "as is"
859  */
860 void migr_do_listdirs(void) {
861         cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
862         cprintf("bio|%s\n",             ctdl_bio_dir);
863         cprintf("files|%s\n",           ctdl_file_dir);
864         cprintf("userpics|%s\n",        ctdl_usrpic_dir);
865         cprintf("messages|%s\n",        ctdl_message_dir);
866         cprintf("netconfigs|%s\n",      ctdl_netcfg_dir);
867         cprintf("keys|%s\n",            ctdl_key_dir);
868         cprintf("images|%s\n",          ctdl_image_dir);
869         cprintf("info|%s\n",            ctdl_info_dir);
870         cprintf("000\n");
871 }
872
873
874 /*
875  * Common code appears in this section
876  */
877
878
879 void cmd_migr(char *cmdbuf) {
880         char cmd[32];
881         
882         if (CtdlAccessCheck(ac_internal)) return;
883         
884         if (CtdlTrySingleUser())
885         {
886                 CtdlDisableHouseKeeping();
887                 CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
888                 CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
889
890                 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
891                 if (!strcasecmp(cmd, "export")) {
892                         migr_do_export();
893                 }
894                 else if (!strcasecmp(cmd, "import")) {
895                         migr_do_import();
896                 }
897                 else if (!strcasecmp(cmd, "listdirs")) {
898                         migr_do_listdirs();
899                 }
900                 else {
901                         cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
902                 }
903
904                 unlink(migr_tempfilename1);
905                 unlink(migr_tempfilename2);
906
907                 CtdlEnableHouseKeeping();
908                 CtdlEndSingleUser();
909         }
910         else
911         {
912                 cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
913         }
914 }
915
916
917 CTDL_MODULE_INIT(migrate)
918 {
919         if (!threading)
920         {
921                 CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
922         }
923         
924         /* return our module name for the log */
925         return "migrate";
926 }