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