More removal of $Id$ tags
[citadel.git] / citadel / utils / ctdlmigrate.c
1 /*
2  * Across-the-wire migration utility for Citadel
3  *
4  * Yes, we used goto, and gets(), and committed all sorts of other heinous sins here.
5  * The scope of this program isn't wide enough to make a difference.  If you don't like
6  * it you can rewrite it.
7  *
8  * Copyright (c) 2009 citadel.org
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 3 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  *
24  * (Note: a useful future enhancement might be to support "-h" on both sides)
25  *
26  */
27
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <fcntl.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/utsname.h>
37 #include <sys/wait.h>
38 #include <signal.h>
39 #include <netdb.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <pwd.h>
43 #include <time.h>
44 #include <libcitadel.h>
45 #include "citadel.h"
46 #include "axdefs.h"
47 #include "sysdep.h"
48 #include "config.h"
49 #include "citadel_dirs.h"
50 #if HAVE_BACKTRACE
51 #include <execinfo.h>
52 #endif
53
54
55
56 /*
57  * Replacement for gets() that doesn't throw a compiler warning.
58  * We're only using it for some simple prompts, so we don't need
59  * to worry about attackers exploiting it.
60  */
61 void getz(char *buf) {
62         char *ptr;
63
64         ptr = fgets(buf, 32767, stdin);
65         if (!ptr) {
66                 buf[0] = 0;
67                 return;
68         }
69
70         ptr = strchr(buf, '\n');
71         if (ptr) *ptr = 0;
72 }
73
74
75
76
77
78 int main(int argc, char *argv[])
79 {
80         int relh=0;
81         int home=0;
82         char relhome[PATH_MAX]="";
83         char ctdldir[PATH_MAX]=CTDLDIR;
84         char yesno[5];
85         char sendcommand[PATH_MAX];
86         int cmdexit;
87         char cmd[PATH_MAX];
88         char buf[PATH_MAX];
89         char socket_path[PATH_MAX];
90         char remote_user[256];
91         char remote_host[256];
92         char remote_sendcommand[PATH_MAX];
93         FILE *sourcefp = NULL;
94         FILE *targetfp = NULL;
95         int linecount = 0;
96         char spinning[4] = "-\\|/" ;
97         int exitcode = 0;
98         
99         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
100         CtdlMakeTempFileName(socket_path, sizeof socket_path);
101
102         cmdexit = system("clear");
103         printf( "-------------------------------------------\n"
104                 "Over-the-wire migration utility for Citadel\n"
105                 "-------------------------------------------\n"
106                 "\n"
107                 "This utility is designed to migrate your Citadel installation\n"
108                 "to a new host system via a network connection, without disturbing\n"
109                 "the source system.  The target may be a different CPU architecture\n"
110                 "and/or operating system.  The source system should be running\n"
111                 "Citadel %d.%02d or newer, and the target system should be running\n"
112                 "either the same version or a newer version.  You will also need\n"
113                 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
114                 "\n"
115                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
116                 "on this system will be ERASED.\n"
117                 "\n"
118                 "Do you wish to continue? "
119                 ,
120                 EXPORT_REV_MIN / 100,
121                 EXPORT_REV_MIN % 100
122         );
123
124         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
125                 exit(0);
126         }
127
128         printf("\n\nGreat!  First we will check some things out here on our target\n"
129                 "system to make sure it is ready to receive data.\n\n");
130
131         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
132         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
133         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
134         cmdexit = system(cmd);
135         if (cmdexit != 0) {
136                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
137                         "here on the target system.  Is Citadel running?\n\n");
138                 exit(1);
139         }
140         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
141
142         printf("Enter the host name or IP address of the source system\n"
143                 "(example: ctdl.foo.org)\n"
144                 "--> ");
145         getz(remote_host);
146         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
147                 "(usually root)\n--> ",
148                 remote_host);
149         getz(remote_user);
150
151         printf("\nEstablishing an SSH connection to the source system...\n\n");
152         unlink(socket_path);
153         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
154         cmdexit = system(cmd);
155         printf("\n");
156         if (cmdexit != 0) {
157                 printf("This program was unable to establish an SSH session to the source system.\n\n");
158                 exitcode = cmdexit;
159                 goto THEEND;
160         }
161
162         printf("\nTesting a command over the connection...\n\n");
163         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
164                 socket_path, remote_user, remote_host);
165         cmdexit = system(cmd);
166         printf("\n");
167         if (cmdexit != 0) {
168                 printf("Remote commands are not succeeding.\n\n");
169                 exitcode = cmdexit;
170                 goto THEEND;
171         }
172
173         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
174         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
175         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
176                 socket_path, remote_user, remote_host, remote_sendcommand);
177         cmdexit = system(cmd);
178         if (cmdexit != 0) {
179                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
180                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
181                         socket_path, remote_user, remote_host, remote_sendcommand);
182                 cmdexit = system(cmd);
183         }
184         if (cmdexit != 0) {
185                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
186                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
187                         "(example: /opt/foo/citadel)\n"
188                         "--> ", remote_host);
189                 getz(buf);
190                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
191                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
192                         socket_path, remote_user, remote_host, remote_sendcommand);
193                 cmdexit = system(cmd);
194         }
195         printf("\n");
196         if (cmdexit != 0) {
197                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
198                 exitcode = cmdexit;
199                 goto THEEND;
200         }
201
202         printf("ctdlmigrate will now begin a database migration...\n");
203
204         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
205                 socket_path, remote_user, remote_host, remote_sendcommand);
206         sourcefp = popen(cmd, "r");
207         if (!sourcefp) {
208                 printf("\n%s\n\n", strerror(errno));
209                 exitcode = 2;
210                 goto THEEND;
211         }
212
213         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
214         targetfp = popen(cmd, "w");
215         if (!targetfp) {
216                 printf("\n%s\n\n", strerror(errno));
217                 exitcode = 3;
218                 goto THEEND;
219         }
220
221         while (fgets(buf, sizeof buf, sourcefp) != NULL) {
222                 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
223                         exitcode = 4;
224                         printf("%s\n", strerror(errno));
225                         goto FAIL;
226                 }
227                 ++linecount;
228                 if ((linecount % 100) == 0) {
229                         printf("%c\r", spinning[((linecount / 100) % 4)]);
230                         fflush(stdout);
231                 }
232         }
233
234 FAIL:   if (sourcefp) pclose(sourcefp);
235         if (targetfp) pclose(targetfp);
236         if (exitcode != 0) goto THEEND;
237
238         /* We need to copy a bunch of other stuff, and will do so using rsync */
239
240         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
241                 socket_path, remote_user, remote_host, remote_sendcommand);
242         sourcefp = popen(cmd, "r");
243         if (!sourcefp) {
244                 printf("\n%s\n\n", strerror(errno));
245                 exitcode = 2;
246                 goto THEEND;
247         }
248         while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
249                 buf[strlen(buf)-1] = 0;
250
251                 if (!strncasecmp(buf, "bio|", 4)) {
252                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
253                                 socket_path, remote_user, remote_host, &buf[4], ctdl_bio_dir);
254                 }
255                 else if (!strncasecmp(buf, "files|", 6)) {
256                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
257                                 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
258                 }
259                 else if (!strncasecmp(buf, "userpics|", 9)) {
260                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
261                                 socket_path, remote_user, remote_host, &buf[9], ctdl_usrpic_dir);
262                 }
263                 else if (!strncasecmp(buf, "messages|", 9)) {
264                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
265                                 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
266                 }
267                 else if (!strncasecmp(buf, "netconfigs|", 11)) {
268                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
269                                 socket_path, remote_user, remote_host, &buf[11], ctdl_netcfg_dir);
270                 }
271                 else if (!strncasecmp(buf, "keys|", 5)) {
272                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
273                                 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
274                 }
275                 else if (!strncasecmp(buf, "images|", 7)) {
276                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
277                                 socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir);
278                 }
279                 else if (!strncasecmp(buf, "info|", 5)) {
280                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
281                                 socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir);
282                 }
283                 else {
284                         strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
285                 }
286                 printf("%s\n", cmd);
287                 cmdexit = system(cmd);
288                 if (cmdexit != 0) {
289                         exitcode += cmdexit;
290                 }
291         }
292         pclose(sourcefp);
293
294 THEEND: if (exitcode == 0) {
295                 printf("\n\n *** Citadel migration was successful! *** \n\n");
296         }
297         else {
298                 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
299         }
300         printf("\nShutting down the socket connection...\n\n");
301         snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
302                 socket_path, remote_user, remote_host);
303         cmdexit = system(cmd);
304         printf("\n");
305         if (cmdexit != 0) {
306                 printf("There was an error shutting down the socket.\n\n");
307                 exitcode = cmdexit;
308         }
309
310         unlink(socket_path);
311         exit(exitcode);
312 }