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