d89a77e5b38b879814e1a37fc1d36a0be5415f83
[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 %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
141 get_remote_user:
142         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
143                 "(usually root)\n--> ",
144                 remote_host);
145         getz(remote_user);
146         if (IsEmptyStr(remote_user))
147                 goto get_remote_user;
148
149         printf("\nEstablishing an SSH connection to the source system...\n\n");
150         unlink(socket_path);
151         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
152         cmdexit = system(cmd);
153         printf("\n");
154         if (cmdexit != 0) {
155                 printf("This program was unable to establish an SSH session to the source system.\n\n");
156                 exitcode = cmdexit;
157                 goto THEEND;
158         }
159
160         printf("\nTesting a command over the connection...\n\n");
161         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
162                 socket_path, remote_user, remote_host);
163         cmdexit = system(cmd);
164         printf("\n");
165         if (cmdexit != 0) {
166                 printf("Remote commands are not succeeding.\n\n");
167                 exitcode = cmdexit;
168                 goto THEEND;
169         }
170
171         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
172         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
173         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
174                 socket_path, remote_user, remote_host, remote_sendcommand);
175         cmdexit = system(cmd);
176         if (cmdexit != 0) {
177                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
178                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
179                         socket_path, remote_user, remote_host, remote_sendcommand);
180                 cmdexit = system(cmd);
181         }
182         if (cmdexit != 0) {
183                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
184                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
185                         "(example: /opt/foo/citadel)\n"
186                         "--> ", remote_host);
187                 getz(buf);
188                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
189                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
190                         socket_path, remote_user, remote_host, remote_sendcommand);
191                 cmdexit = system(cmd);
192         }
193         printf("\n");
194         if (cmdexit != 0) {
195                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
196                 exitcode = cmdexit;
197                 goto THEEND;
198         }
199
200         printf("ctdlmigrate will now begin a database migration...\n");
201         printf("  if the system doesn't start working, \n");
202         printf("  have a look at the syslog for pending jobs needing to be terminated.\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, "files|", 6)) {
252                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
253                                 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
254                 }
255                 else if (!strncasecmp(buf, "messages|", 9)) {
256                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
257                                 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
258                 }
259                 else if (!strncasecmp(buf, "keys|", 5)) {
260                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
261                                 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
262                 }
263                 else if (!strncasecmp(buf, "images|", 7)) {
264                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
265                                 socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir);
266                 }
267                 else if (!strncasecmp(buf, "info|", 5)) {
268                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
269                                 socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir);
270                 }
271                 else {
272                         strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
273                 }
274                 printf("%s\n", cmd);
275                 cmdexit = system(cmd);
276                 if (cmdexit != 0) {
277                         exitcode += cmdexit;
278                 }
279         }
280         pclose(sourcefp);
281
282 THEEND: if (exitcode == 0) {
283                 printf("\n\n *** Citadel migration was successful! *** \n\n");
284         }
285         else {
286                 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
287         }
288         printf("\nShutting down the socket connection...\n\n");
289         snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
290                 socket_path, remote_user, remote_host);
291         cmdexit = system(cmd);
292         printf("\n");
293         if (cmdexit != 0) {
294                 printf("There was an error shutting down the socket.\n\n");
295                 exitcode = cmdexit;
296         }
297
298         unlink(socket_path);
299         exit(exitcode);
300 }