c3646c3e41614c45e64b752e5e7bf843ea9ab81e
[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-2016 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 version %d 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
115         );
116
117         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
118                 exit(0);
119         }
120
121         printf("\n\nGreat!  First we will check some things out here on our target\n"
122                 "system to make sure it is ready to receive data.\n\n");
123
124         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
125         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
126         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
127         cmdexit = system(cmd);
128         if (cmdexit != 0) {
129                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
130                         "here on the target system.  Is Citadel running?\n\n");
131                 exit(1);
132         }
133         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
134
135         printf("Enter the host name or IP address of the source system\n"
136                 "(example: ctdl.foo.org)\n"
137                 "--> ");
138         getz(remote_host);
139
140 get_remote_user:
141         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
142                 "(usually root)\n--> ",
143                 remote_host);
144         getz(remote_user);
145         if (IsEmptyStr(remote_user))
146                 goto get_remote_user;
147
148         printf("\nEstablishing an SSH connection to the source system...\n\n");
149         unlink(socket_path);
150         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
151         cmdexit = system(cmd);
152         printf("\n");
153         if (cmdexit != 0) {
154                 printf("This program was unable to establish an SSH session to the source system.\n\n");
155                 exitcode = cmdexit;
156                 goto THEEND;
157         }
158
159         printf("\nTesting a command over the connection...\n\n");
160         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
161                 socket_path, remote_user, remote_host);
162         cmdexit = system(cmd);
163         printf("\n");
164         if (cmdexit != 0) {
165                 printf("Remote commands are not succeeding.\n\n");
166                 exitcode = cmdexit;
167                 goto THEEND;
168         }
169
170         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
171         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
172         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
173                 socket_path, remote_user, remote_host, remote_sendcommand);
174         cmdexit = system(cmd);
175         if (cmdexit != 0) {
176                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
177                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
178                         socket_path, remote_user, remote_host, remote_sendcommand);
179                 cmdexit = system(cmd);
180         }
181         if (cmdexit != 0) {
182                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
183                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
184                         "(example: /opt/foo/citadel)\n"
185                         "--> ", remote_host);
186                 getz(buf);
187                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
188                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
189                         socket_path, remote_user, remote_host, remote_sendcommand);
190                 cmdexit = system(cmd);
191         }
192         printf("\n");
193         if (cmdexit != 0) {
194                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
195                 exitcode = cmdexit;
196                 goto THEEND;
197         }
198
199         printf("ctdlmigrate will now begin a database migration...\n");
200         printf("  if the system doesn't start working, \n");
201         printf("  have a look at the syslog for pending jobs needing to be terminated.\n");
202
203         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
204                 socket_path, remote_user, remote_host, remote_sendcommand);
205         sourcefp = popen(cmd, "r");
206         if (!sourcefp) {
207                 printf("\n%s\n\n", strerror(errno));
208                 exitcode = 2;
209                 goto THEEND;
210         }
211
212         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
213         targetfp = popen(cmd, "w");
214         if (!targetfp) {
215                 printf("\n%s\n\n", strerror(errno));
216                 exitcode = 3;
217                 goto THEEND;
218         }
219
220         while (fgets(buf, sizeof buf, sourcefp) != NULL) {
221                 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
222                         exitcode = 4;
223                         printf("%s\n", strerror(errno));
224                         goto FAIL;
225                 }
226                 ++linecount;
227                 if ((linecount % 100) == 0) {
228                         printf("%c\r", spinning[((linecount / 100) % 4)]);
229                         fflush(stdout);
230                 }
231         }
232
233 FAIL:   if (sourcefp) pclose(sourcefp);
234         if (targetfp) pclose(targetfp);
235         if (exitcode != 0) goto THEEND;
236
237         /* We need to copy a bunch of other stuff, and will do so using rsync */
238
239         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
240                 socket_path, remote_user, remote_host, remote_sendcommand);
241         sourcefp = popen(cmd, "r");
242         if (!sourcefp) {
243                 printf("\n%s\n\n", strerror(errno));
244                 exitcode = 2;
245                 goto THEEND;
246         }
247         while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
248                 buf[strlen(buf)-1] = 0;
249
250                 if (!strncasecmp(buf, "files|", 6)) {
251                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
252                                 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
253                 }
254                 else if (!strncasecmp(buf, "messages|", 9)) {
255                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
256                                 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
257                 }
258                 else if (!strncasecmp(buf, "keys|", 5)) {
259                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
260                                 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
261                 }
262                 else if (!strncasecmp(buf, "images|", 7)) {
263                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
264                                 socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir);
265                 }
266                 else if (!strncasecmp(buf, "info|", 5)) {
267                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
268                                 socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir);
269                 }
270                 else {
271                         strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
272                 }
273                 printf("%s\n", cmd);
274                 cmdexit = system(cmd);
275                 if (cmdexit != 0) {
276                         exitcode += cmdexit;
277                 }
278         }
279         pclose(sourcefp);
280
281 THEEND: if (exitcode == 0) {
282                 printf("\n\n *** Citadel migration was successful! *** \n\n");
283         }
284         else {
285                 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
286         }
287         printf("\nShutting down the socket connection...\n\n");
288         snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
289                 socket_path, remote_user, remote_host);
290         cmdexit = system(cmd);
291         printf("\n");
292         if (cmdexit != 0) {
293                 printf("There was an error shutting down the socket.\n\n");
294                 exitcode = cmdexit;
295         }
296
297         unlink(socket_path);
298         exit(exitcode);
299 }