* If running 7.60 code for the first time, serv_upgrade erases any existing LDAP...
[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  * FIXME handle -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         pid_t sshpid;
89         
90         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
91         CtdlMakeTempFileName(socket_path, sizeof socket_path);
92
93         cmdexit = system("clear");
94         printf( "-------------------------------------------\n"
95                 "Over-the-wire migration utility for Citadel\n"
96                 "-------------------------------------------\n"
97                 "\n"
98                 "This utility is designed to migrate your Citadel installation\n"
99                 "to a new host system via a network connection, without disturbing\n"
100                 "the source system.  The target may be a different CPU architecture\n"
101                 "and/or operating system.  The source system should be running\n"
102                 "Citadel %d.%02d or newer, and the target system should be running\n"
103                 "either the same version or a newer version.  You will also need\n"
104                 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
105                 "\n"
106                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
107                 "on this system will be ERASED.\n"
108                 "\n"
109                 "Do you wish to continue? "
110                 ,
111                 EXPORT_REV_MIN / 100,
112                 EXPORT_REV_MIN % 100
113         );
114
115         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
116                 exit(0);
117         }
118
119         printf("\n\nGreat!  First we will check some things out here on our target\n"
120                 "system to make sure it is ready to receive data.\n\n");
121
122         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
123         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
124         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
125         cmdexit = system(cmd);
126         if (cmdexit != 0) {
127                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
128                         "here on the target system.  Is Citadel running?\n\n");
129                 exit(1);
130         }
131         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
132
133         printf("Enter the host name or IP address of the source system\n"
134                 "(example: ctdl.foo.org)\n"
135                 "--> ");
136         getz(remote_host);
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
142         sshpid = fork();
143         if (sshpid < 0)
144         {
145                 printf("\n%s\n", strerror(errno));
146                 exitcode = errno;
147                 goto THEEND;
148         }
149         else if (sshpid == 0)
150         {
151                 printf("\nEstablishing an SSH connection to the source system...\n\n");
152                 unlink(socket_path);
153                 snprintf(cmd, sizeof cmd, "%s@%s", remote_user, remote_host);
154                 execlp("ssh", "ssh", "-MNf", "-S", socket_path, cmd, NULL);
155                 cmdexit = errno;
156                 printf("\n%s\n", strerror(cmdexit));
157                 exit(cmdexit);          /* child process exits */
158         }
159
160         /* If we get here we are the parent process */
161         if (waitpid(sshpid, &cmdexit, 0) <= 0) {
162                 exitcode = errno;
163                 printf("\n%s\n", strerror(errno));
164                 goto THEEND;
165         }
166
167         if (WIFSIGNALED(cmdexit)) {
168                 exitcode = errno;
169                 printf("\n%s\n", strerror(errno));
170                 goto THEEND;
171         }
172
173         if ((WIFEXITED(cmdexit)) && (WEXITSTATUS(cmdexit) != 0)) {
174                 exitcode = WEXITSTATUS(cmdexit);
175                 printf("\n%s\n", strerror(errno));
176                 goto THEEND;
177         }
178
179         printf("\nTesting a command over the connection...\n\n");
180         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
181                 socket_path, remote_user, remote_host);
182         cmdexit = system(cmd);
183         printf("\n");
184         if (cmdexit != 0) {
185                 printf("Remote commands are not succeeding.\n\n");
186                 exitcode = cmdexit;
187                 goto THEEND;
188         }
189
190         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
191         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
192         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
193                 socket_path, remote_user, remote_host, remote_sendcommand);
194         cmdexit = system(cmd);
195         if (cmdexit != 0) {
196                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
197                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
198                         socket_path, remote_user, remote_host, remote_sendcommand);
199                 cmdexit = system(cmd);
200         }
201         if (cmdexit != 0) {
202                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
203                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
204                         "(example: /opt/foo/citadel)\n"
205                         "--> ", remote_host);
206                 getz(buf);
207                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
208                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
209                         socket_path, remote_user, remote_host, remote_sendcommand);
210                 cmdexit = system(cmd);
211         }
212         printf("\n");
213         if (cmdexit != 0) {
214                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
215                 exitcode = cmdexit;
216                 goto THEEND;
217         }
218
219         printf("ctdlmigrate will now begin a database migration...\n");
220
221         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
222                 socket_path, remote_user, remote_host, remote_sendcommand);
223         sourcefp = popen(cmd, "r");
224         if (!sourcefp) {
225                 printf("\n%s\n\n", strerror(errno));
226                 exitcode = 2;
227                 goto THEEND;
228         }
229
230         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
231         targetfp = popen(cmd, "w");
232         if (!targetfp) {
233                 printf("\n%s\n\n", strerror(errno));
234                 exitcode = 3;
235                 goto THEEND;
236         }
237
238         while (fgets(buf, sizeof buf, sourcefp) != NULL) {
239                 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
240                         exitcode = 4;
241                         printf("%s\n", strerror(errno));
242                         goto FAIL;
243                 }
244                 ++linecount;
245                 if ((linecount % 100) == 0) {
246                         printf("%c\r", spinning[((linecount / 100) % 4)]);
247                         fflush(stdout);
248                 }
249         }
250
251 FAIL:   if (sourcefp) pclose(sourcefp);
252         if (targetfp) pclose(targetfp);
253         if (exitcode != 0) goto THEEND;
254
255         /* We need to copy a bunch of other stuff, and will do so using rsync */
256
257         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
258                 socket_path, remote_user, remote_host, remote_sendcommand);
259         sourcefp = popen(cmd, "r");
260         if (!sourcefp) {
261                 printf("\n%s\n\n", strerror(errno));
262                 exitcode = 2;
263                 goto THEEND;
264         }
265         while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
266                 buf[strlen(buf)-1] = 0;
267
268                 if (!strncasecmp(buf, "bio|", 4)) {
269                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
270                                 socket_path, remote_user, remote_host, &buf[4], ctdl_bio_dir);
271                 }
272                 else if (!strncasecmp(buf, "files|", 6)) {
273                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
274                                 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
275                 }
276                 else if (!strncasecmp(buf, "userpics|", 9)) {
277                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
278                                 socket_path, remote_user, remote_host, &buf[9], ctdl_usrpic_dir);
279                 }
280                 else if (!strncasecmp(buf, "messages|", 9)) {
281                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
282                                 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
283                 }
284                 else if (!strncasecmp(buf, "netconfigs|", 11)) {
285                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
286                                 socket_path, remote_user, remote_host, &buf[11], ctdl_netcfg_dir);
287                 }
288                 else if (!strncasecmp(buf, "keys|", 5)) {
289                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
290                                 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
291                 }
292                 else if (!strncasecmp(buf, "images|", 7)) {
293                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
294                                 socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir);
295                 }
296                 else if (!strncasecmp(buf, "info|", 5)) {
297                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
298                                 socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir);
299                 }
300                 else {
301                         strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
302                 }
303                 printf("%s\n", cmd);
304                 cmdexit = system(cmd);
305                 if (cmdexit != 0) {
306                         exitcode += cmdexit;
307                 }
308         }
309         pclose(sourcefp);
310
311 THEEND: kill(sshpid, SIGKILL);
312         unlink(socket_path);
313         exit(exitcode);
314 }