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