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