5008cc5e4d89c1a0fdff98851b9f9b014a11eab8
[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-2021 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
45
46
47 /*
48  * Replacement for gets() that doesn't throw a compiler warning.
49  * We're only using it for some simple prompts, so we don't need
50  * to worry about attackers exploiting it.
51  */
52 void getz(char *buf) {
53         char *ptr;
54
55         ptr = fgets(buf, SIZ, stdin);
56         if (!ptr) {
57                 buf[0] = 0;
58                 return;
59         }
60
61         ptr = strchr(buf, '\n');
62         if (ptr) *ptr = 0;
63 }
64
65
66
67
68
69 int main(int argc, char *argv[])
70 {
71         int relh=0;
72         int home=0;
73         char relhome[PATH_MAX]="";
74         char ctdldir[PATH_MAX]=CTDLDIR;
75         char yesno[5];
76         char sendcommand[PATH_MAX];
77         int cmdexit;
78         char cmd[PATH_MAX];
79         char buf[PATH_MAX];
80         char socket_path[PATH_MAX];
81         char remote_user[SIZ];
82         char remote_host[SIZ];
83         char remote_sendcommand[PATH_MAX];
84         FILE *sourcefp = NULL;
85         FILE *targetfp = NULL;
86         int linecount = 0;
87         char spinning[4] = "-\\|/" ;
88         int exitcode = 0;
89         
90         CtdlMakeTempFileName(socket_path, sizeof socket_path);
91
92         cmdexit = system("clear");
93         printf( "-------------------------------------------\n"
94                 "Over-the-wire migration utility for Citadel\n"
95                 "-------------------------------------------\n"
96                 "\n"
97                 "This utility is designed to migrate your Citadel installation\n"
98                 "to a new host system via a network connection, without disturbing\n"
99                 "the source system.  The target may be a different CPU architecture\n"
100                 "and/or operating system.  The source system should be running\n"
101                 "Citadel version %d or newer, and the target system should be running\n"
102                 "either the same version or a newer version.  You will also need\n"
103                 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
104                 "\n"
105                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
106                 "on this system will be ERASED.\n"
107                 "\n"
108                 "Do you wish to continue? "
109                 ,
110                 EXPORT_REV_MIN
111         );
112
113         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
114                 exit(0);
115         }
116
117         printf("\n\nGreat!  First we will check some things out here on our target\n"
118                 "system to make sure it is ready to receive data.\n\n");
119
120         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
121         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
122         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
123         cmdexit = system(cmd);
124         if (cmdexit != 0) {
125                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
126                         "here on the target system.  Is Citadel running?\n\n");
127                 exit(1);
128         }
129         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
130
131         printf("Enter the host name or IP address of the source system\n"
132                 "(example: ctdl.foo.org)\n"
133                 "--> ");
134         getz(remote_host);
135
136 get_remote_user:
137         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
138                 "(usually root)\n--> ",
139                 remote_host);
140         getz(remote_user);
141         if (IsEmptyStr(remote_user))
142                 goto get_remote_user;
143
144         printf("\nEstablishing an SSH connection to the source system...\n\n");
145         unlink(socket_path);
146         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
147         cmdexit = system(cmd);
148         printf("\n");
149         if (cmdexit != 0) {
150                 printf("This program was unable to establish an SSH session to the source system.\n\n");
151                 exitcode = cmdexit;
152                 goto THEEND;
153         }
154
155         printf("\nTesting a command over the connection...\n\n");
156         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
157                 socket_path, remote_user, remote_host);
158         cmdexit = system(cmd);
159         printf("\n");
160         if (cmdexit != 0) {
161                 printf("Remote commands are not succeeding.\n\n");
162                 exitcode = cmdexit;
163                 goto THEEND;
164         }
165
166         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
167         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
168         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
169                 socket_path, remote_user, remote_host, remote_sendcommand);
170         cmdexit = system(cmd);
171         if (cmdexit != 0) {
172                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/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         }
177         if (cmdexit != 0) {
178                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
179                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
180                         "(example: /opt/foo/citadel)\n"
181                         "--> ", remote_host);
182                 getz(buf);
183                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
184                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
185                         socket_path, remote_user, remote_host, remote_sendcommand);
186                 cmdexit = system(cmd);
187         }
188         printf("\n");
189         if (cmdexit != 0) {
190                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
191                 exitcode = cmdexit;
192                 goto THEEND;
193         }
194
195         printf("ctdlmigrate will now begin a database migration...\n");
196         printf("  if the system doesn't start working, \n");
197         printf("  have a look at the syslog for pending jobs needing to be terminated.\n");
198
199         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
200                 socket_path, remote_user, remote_host, remote_sendcommand);
201         sourcefp = popen(cmd, "r");
202         if (!sourcefp) {
203                 printf("\n%s\n\n", strerror(errno));
204                 exitcode = 2;
205                 goto THEEND;
206         }
207
208         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
209         targetfp = popen(cmd, "w");
210         if (!targetfp) {
211                 printf("\n%s\n\n", strerror(errno));
212                 exitcode = 3;
213                 goto THEEND;
214         }
215
216         while (fgets(buf, sizeof buf, sourcefp) != NULL) {
217                 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
218                         exitcode = 4;
219                         printf("%s\n", strerror(errno));
220                         goto FAIL;
221                 }
222                 ++linecount;
223                 if ((linecount % 100) == 0) {
224                         printf("%c\r", spinning[((linecount / 100) % 4)]);
225                         fflush(stdout);
226                 }
227         }
228
229 FAIL:   if (sourcefp) pclose(sourcefp);
230         if (targetfp) pclose(targetfp);
231         if (exitcode != 0) goto THEEND;
232
233         /* We need to copy a bunch of other stuff, and will do so using rsync */
234
235         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
236                 socket_path, remote_user, remote_host, remote_sendcommand);
237         sourcefp = popen(cmd, "r");
238         if (!sourcefp) {
239                 printf("\n%s\n\n", strerror(errno));
240                 exitcode = 2;
241                 goto THEEND;
242         }
243         while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
244                 buf[strlen(buf)-1] = 0;
245
246                 if (!strncasecmp(buf, "files|", 6)) {
247                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
248                                 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
249                 }
250                 else if (!strncasecmp(buf, "messages|", 9)) {
251                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
252                                 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
253                 }
254                 else if (!strncasecmp(buf, "keys|", 5)) {
255                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
256                                 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
257                 }
258                 else {
259                         strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
260                 }
261                 printf("%s\n", cmd);
262                 cmdexit = system(cmd);
263                 if (cmdexit != 0) {
264                         exitcode += cmdexit;
265                 }
266         }
267         pclose(sourcefp);
268
269 THEEND: if (exitcode == 0) {
270                 printf("\n\n *** Citadel migration was successful! *** \n\n");
271         }
272         else {
273                 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
274         }
275         printf("\nShutting down the socket connection...\n\n");
276         snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
277                 socket_path, remote_user, remote_host);
278         cmdexit = system(cmd);
279         printf("\n");
280         if (cmdexit != 0) {
281                 printf("There was an error shutting down the socket.\n\n");
282                 exitcode = cmdexit;
283         }
284
285         unlink(socket_path);
286         exit(exitcode);
287 }