7d9313604fa6e5a7f0ea38d5f9cccf4a577ce4f0
[citadel.git] / citadel / ctdlmigrate.c
1 /*
2  * Across-the-wire migration utility for Citadel
3  *
4  * Copyright (c) 2009-2021 citadel.org
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <ctype.h>
20 #include <fcntl.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/utsname.h>
24 #include <sys/wait.h>
25 #include <signal.h>
26 #include <netdb.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <pwd.h>
30 #include <time.h>
31 #include <sys/socket.h>
32 #include <sys/un.h>
33 #include <libcitadel.h>
34 #include "citadel.h"
35 #include "axdefs.h"
36 #include "sysdep.h"
37 #include "config.h"
38 #include "citadel_dirs.h"
39
40
41 /*
42  * Replacement for gets() that doesn't throw a compiler warning.
43  * We're only using it for some simple prompts, so we don't need
44  * to worry about attackers exploiting it.
45  */
46 void getz(char *buf) {
47         char *ptr;
48
49         ptr = fgets(buf, SIZ, stdin);
50         if (!ptr) {
51                 buf[0] = 0;
52                 return;
53         }
54
55         ptr = strchr(buf, '\n');
56         if (ptr) *ptr = 0;
57 }
58
59
60 // These variables are used by both main() and ctdlmigrate_exit()
61 // They are global so that ctdlmigrate_exit can be called from a signal handler
62 char socket_path[PATH_MAX];
63 pid_t sshpid = (-1);
64
65 void ctdlmigrate_exit(int cmdexit) {
66         unlink(socket_path);
67         if (sshpid > 0) {
68                 printf("Shutting down the SSH session...\n");
69                 kill(sshpid, SIGKILL);
70         }
71         printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
72         exit(cmdexit);
73 }
74
75
76 int uds_connectsock(char *sockpath) {
77         int s;
78         struct sockaddr_un addr;
79
80         memset(&addr, 0, sizeof(addr));
81         addr.sun_family = AF_UNIX;
82         strcpy(addr.sun_path, sockpath);
83
84         s = socket(AF_UNIX, SOCK_STREAM, 0);
85         if (s < 0) {
86                 fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
87                 ctdlmigrate_exit(3);
88         }
89
90         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
91                 fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
92                 close(s);
93                 ctdlmigrate_exit(3);
94         }
95
96         return s;
97 }
98
99
100 /*
101  * input binary data from socket
102  */
103 void serv_read(int serv_sock, char *buf, int bytes) {
104         int len, rlen;
105
106         len = 0;
107         while (len < bytes) {
108                 rlen = read(serv_sock, &buf[len], bytes - len);
109                 if (rlen < 1) {
110                         return;
111                 }
112                 len = len + rlen;
113         }
114 }
115
116
117 /*
118  * send binary to server
119  */
120 void serv_write(int serv_sock, char *buf, int nbytes) {
121         int bytes_written = 0;
122         int retval;
123         while (bytes_written < nbytes) {
124                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
125                 if (retval < 1) {
126                         return;
127                 }
128                 bytes_written = bytes_written + retval;
129         }
130 }
131
132
133 /*
134  * input string from socket - implemented in terms of serv_read()
135  */
136 void serv_gets(int serv_sock, char *buf) {
137         int i;
138
139         /* Read one character at a time.
140          */
141         for (i = 0;; i++) {
142                 serv_read(serv_sock, &buf[i], 1);
143                 if (buf[i] == '\n' || i == (SIZ-1))
144                         break;
145         }
146
147         /* If we got a long line, discard characters until the newline.
148          */
149         if (i == (SIZ-1)) {
150                 while (buf[i] != '\n') {
151                         serv_read(serv_sock, &buf[i], 1);
152                 }
153         }
154
155         /* Strip all trailing nonprintables (crlf)
156          */
157         buf[i] = 0;
158 }
159
160
161 /*
162  * send line to server - implemented in terms of serv_write()
163  */
164 void serv_puts(int serv_sock, char *buf) {
165         serv_write(serv_sock, buf, strlen(buf));
166         serv_write(serv_sock, "\n", 1);
167 }
168
169
170 int main(int argc, char *argv[]) {
171         char ctdldir[PATH_MAX]=CTDLDIR;
172         char yesno[5];
173         int cmdexit = 0;                                // when something fails, set cmdexit to nonzero, and skip to the end
174         char cmd[PATH_MAX];
175         char buf[PATH_MAX];
176         char remote_user[SIZ];
177         char remote_host[SIZ];
178         char remote_sendcommand[PATH_MAX];
179         FILE *sourcefp = NULL;
180         int linecount = 0;
181         int a;
182         int local_admin_socket = (-1);
183
184         /* Parse command line */
185         while ((a = getopt(argc, argv, "h:")) != EOF) {
186                 switch (a) {
187                 case 'h':
188                         strcpy(ctdldir, optarg);
189                         break;
190                 default:
191                         fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
192                         return(1);
193                 }
194         }
195
196         if (chdir(ctdldir) != 0) {
197                 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
198                 exit(errno);
199         }
200
201         signal(SIGINT, ctdlmigrate_exit);
202         signal(SIGQUIT, ctdlmigrate_exit);
203         signal(SIGTERM, ctdlmigrate_exit);
204         signal(SIGSEGV, ctdlmigrate_exit);
205         signal(SIGHUP, ctdlmigrate_exit);
206         signal(SIGPIPE, ctdlmigrate_exit);
207
208         printf( "\033[2J\033[H\n"
209                 "          \033[32m╔═══════════════════════════════════════════════╗\n"
210                 "          ║                                               ║\n"
211                 "          ║    \033[33mCitadel over-the-wire migration utility    \033[32m║\n"
212                 "          ║                                               ║\n"
213                 "          ╚═══════════════════════════════════════════════╝\033[0m\n"
214                 "\n"
215                 "This utility is designed to migrate your Citadel installation\n"
216                 "to a new host system via a network connection, without disturbing\n"
217                 "the source system.  The target may be a different CPU architecture\n"
218                 "and/or operating system.  The source system should be running\n"
219                 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
220                 "either the same version or a newer version.  You will also need\n"
221                 "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\n"
222                 "\n"
223                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
224                 "on this system will be ERASED.  Your target system will be on this\n"
225                 "host in %s.\n"
226                 "\n"
227                 "\033[33mDo you wish to continue? \033[0m"
228                 ,
229                 EXPORT_REV_MIN, ctdldir
230         );
231
232         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
233                 cmdexit = 1;
234         }
235
236         if (!cmdexit) {
237
238                 printf( "\033[2J\033[H\n"
239                         "          \033[32m╔═══════════════════════════════════════════════╗\n"
240                         "          ║                                               ║\n"
241                         "          ║       \033[33mValidate source and target systems\033[32m      ║\n"
242                         "          ║                                               ║\n"
243                         "          ╚═══════════════════════════════════════════════╝\033[0m\n"
244                         "\n\n");
245         
246                 printf("First we must validate that the local target system is running and ready to receive data.\n");
247                 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
248                 local_admin_socket = uds_connectsock("citadel-admin.socket");
249
250                 serv_gets(local_admin_socket, buf);
251                 puts(buf);
252                 if (buf[0] != '2') {
253                         cmdexit = 1;
254                 }
255         }
256
257         if (!cmdexit) {
258                 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
259                 serv_gets(local_admin_socket, buf);
260                 puts(buf);
261                 if (buf[0] != '2') {
262                         cmdexit = 1;
263                 }
264         }
265
266         if (!cmdexit) {
267                 printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
268                 printf("Enter the host name or IP address of the source system\n"
269                         "(example: ctdl.foo.org)\n"
270                         "--> ");
271                 getz(remote_host);
272         
273                 while (IsEmptyStr(remote_user)) {
274                         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
275                                 "(usually root)\n--> ",
276                                 remote_host);
277                         getz(remote_user);
278                 }
279
280                 printf("\nEstablishing an SSH connection to the source system...\n\n");
281                 sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
282                 unlink(socket_path);
283
284                 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
285                 sshpid = fork();
286                 if (sshpid < 0) {
287                         printf("%s\n", strerror(errno));
288                         cmdexit = errno;
289                 }
290                 else if (sshpid == 0) {
291                         execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
292                         exit(1);
293                 }
294                 else {                                          // Wait for SSH to go into the background
295                         waitpid(sshpid, &cmdexit, 0);
296                 }
297         }
298
299         if (!cmdexit) {
300                 printf("\nTesting a command over the connection...\n\n");
301                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
302                         socket_path, remote_user, remote_host);
303                 cmdexit = system(cmd);
304                 printf("\n");
305                 if (cmdexit != 0) {
306                         printf("\033[31mRemote commands are not succeeding.\033[0m\n\n");
307                 }
308         }
309
310         if (!cmdexit) {
311                 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
312                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
313                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
314                 cmdexit = system(cmd);
315
316                 if (cmdexit) {
317                         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
318                         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
319                         cmdexit = system(cmd);
320                 }
321
322                 if (cmdexit) {
323                         printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
324                                 "the name of the directory on %s which contains the 'sendcommand' program.\n"
325                                 "(example: /opt/foo/citadel)\n"
326                                 "--> ", remote_host);
327                         getz(buf);
328                         snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
329                         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
330                         cmdexit = system(cmd);
331                         if (!cmdexit) {
332                                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
333                         }
334                 }
335         }
336
337         if (!cmdexit) {
338                 printf( "\033[2J\033[H\n"
339                         "          \033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
340                         "          ║                                                                   ║\n"
341                         "          ║ \033[33mMigrating from: %-50s\033[32m║\n"
342                         "          ║                                                                   ║\n"
343                         "          ╟═══════════════════════════════════════════════════════════════════╣\n"
344                         "          ║                                                                   ║\n"
345                         "          ║ Lines received: 0                           Percent complete: 0   ║\n"
346                         "          ║                                                                   ║\n"
347                         "          ╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
348                         "\n", remote_host
349                 );
350
351                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
352                 sourcefp = popen(cmd, "r");
353                 if (!sourcefp) {
354                         cmdexit = errno;
355                         printf("\n%s\n\n", strerror(errno));
356                 }
357         }
358
359         if (!cmdexit) {
360                 serv_puts(local_admin_socket, "MIGR import");
361                 serv_gets(local_admin_socket, buf);
362                 if (buf[0] != '4') {
363                         printf("\n\033[31m%s\033[0m\n", buf);
364                         cmdexit = 3;
365                 }
366         }
367
368         if (!cmdexit) {
369                 char *ptr;
370                 time_t last_update = time(NULL);
371                 while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
372                         ptr = strchr(buf, '\n');
373                         if (ptr) *ptr = 0;      // remove the newline character
374                         ++linecount;
375                         if (!strncasecmp(buf, "<progress>", 10)) {
376                                 printf("\033[8;75H\033[33m%d\033[0m\033[20;0H\n", atoi(&buf[10]));
377                         }
378                         if (time(NULL) != last_update) {
379                                 last_update = time(NULL);
380                                 printf("\033[8;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
381                         }
382                         serv_puts(local_admin_socket, buf);
383                 }
384         
385                 serv_puts(local_admin_socket, "000");
386         }
387
388         // FIXME restart the local server now
389
390         if (sourcefp) {
391                 printf("Closing the data connection from the source system...\n");
392                 pclose(sourcefp);
393         }
394
395         ctdlmigrate_exit(cmdexit);
396 }