ctdlmigrate now uses a direct socket connection to the local server instead of sendco...
[citadel.git] / citadel / 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 <sys/socket.h>
39 #include <sys/un.h>
40 #include <libcitadel.h>
41 #include "citadel.h"
42 #include "axdefs.h"
43 #include "sysdep.h"
44 #include "config.h"
45 #include "citadel_dirs.h"
46
47
48
49 /*
50  * Replacement for gets() that doesn't throw a compiler warning.
51  * We're only using it for some simple prompts, so we don't need
52  * to worry about attackers exploiting it.
53  */
54 void getz(char *buf) {
55         char *ptr;
56
57         ptr = fgets(buf, SIZ, stdin);
58         if (!ptr) {
59                 buf[0] = 0;
60                 return;
61         }
62
63         ptr = strchr(buf, '\n');
64         if (ptr) *ptr = 0;
65 }
66
67
68 int uds_connectsock(char *sockpath) {
69         int s;
70         struct sockaddr_un addr;
71
72         memset(&addr, 0, sizeof(addr));
73         addr.sun_family = AF_UNIX;
74         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
75
76         s = socket(AF_UNIX, SOCK_STREAM, 0);
77         if (s < 0) {
78                 fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
79                 exit(3);
80         }
81
82         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
83                 fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
84                 close(s);
85                 exit(3);
86         }
87
88         return s;
89 }
90
91
92 /*
93  * input binary data from socket
94  */
95 void serv_read(int serv_sock, char *buf, int bytes) {
96         int len, rlen;
97
98         len = 0;
99         while (len < bytes) {
100                 rlen = read(serv_sock, &buf[len], bytes - len);
101                 if (rlen < 1) {
102                         return;
103                 }
104                 len = len + rlen;
105         }
106 }
107
108
109 /*
110  * send binary to server
111  */
112 void serv_write(int serv_sock, char *buf, int nbytes) {
113         int bytes_written = 0;
114         int retval;
115         while (bytes_written < nbytes) {
116                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
117                 if (retval < 1) {
118                         return;
119                 }
120                 bytes_written = bytes_written + retval;
121         }
122 }
123
124
125 /*
126  * input string from socket - implemented in terms of serv_read()
127  */
128 void serv_gets(int serv_sock, char *buf) {
129         int i;
130
131         /* Read one character at a time.
132          */
133         for (i = 0;; i++) {
134                 serv_read(serv_sock, &buf[i], 1);
135                 if (buf[i] == '\n' || i == (SIZ-1))
136                         break;
137         }
138
139         /* If we got a long line, discard characters until the newline.
140          */
141         if (i == (SIZ-1)) {
142                 while (buf[i] != '\n') {
143                         serv_read(serv_sock, &buf[i], 1);
144                 }
145         }
146
147         /* Strip all trailing nonprintables (crlf)
148          */
149         buf[i] = 0;
150 }
151
152
153 /*
154  * send line to server - implemented in terms of serv_write()
155  */
156 void serv_puts(int serv_sock, char *buf) {
157         serv_write(serv_sock, buf, strlen(buf));
158         serv_write(serv_sock, "\n", 1);
159 }
160
161
162 int main(int argc, char *argv[]) {
163         char ctdldir[PATH_MAX]=CTDLDIR;
164         char yesno[5];
165         int cmdexit;
166         char cmd[PATH_MAX];
167         char buf[PATH_MAX];
168         char socket_path[PATH_MAX];
169         char remote_user[SIZ];
170         char remote_host[SIZ];
171         char remote_sendcommand[PATH_MAX];
172         FILE *sourcefp = NULL;
173         int linecount = 0;
174         int a;
175         int local_admin_socket = (-1);
176
177         /* Parse command line */
178         while ((a = getopt(argc, argv, "h:")) != EOF) {
179                 switch (a) {
180                 case 'h':
181                         strncpy(ctdldir, optarg, sizeof ctdldir);
182                         break;
183                 default:
184                         fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
185                         return(1);
186                 }
187         }
188
189         if (chdir(ctdldir) != 0) {
190                 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
191                 exit(errno);
192         }
193
194         printf( "\033[2J\033[H\n"
195                 "-------------------------------------------\n"
196                 "Over-the-wire migration utility for Citadel\n"
197                 "-------------------------------------------\n"
198                 "\n"
199                 "This utility is designed to migrate your Citadel installation\n"
200                 "to a new host system via a network connection, without disturbing\n"
201                 "the source system.  The target may be a different CPU architecture\n"
202                 "and/or operating system.  The source system should be running\n"
203                 "Citadel version %d or newer, and the target system should be running\n"
204                 "either the same version or a newer version.  You will also need\n"
205                 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
206                 "\n"
207                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
208                 "on this system will be ERASED.  Your target system will be on this\n"
209                 "host in %s.\n"
210                 "\n"
211                 "Do you wish to continue? "
212                 ,
213                 EXPORT_REV_MIN, ctdldir
214         );
215
216         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
217                 exit(0);
218         }
219
220         printf("\n\nGreat!  First we will check some things out here on our target\n"
221                 "system to make sure it is ready to receive data.\n\n");
222
223         printf("Checking connectivity to Citadel in %s...\n", ctdldir);
224         local_admin_socket = uds_connectsock("citadel-admin.socket");
225
226
227         serv_gets(local_admin_socket, buf);
228         puts(buf);
229         if (buf[0] != '2') {
230                 exit(1);
231         }
232         serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
233         serv_gets(local_admin_socket, buf);
234         puts(buf);
235         if (buf[0] != '2') {
236                 exit(1);
237         }
238
239         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
240
241         printf("Enter the host name or IP address of the source system\n"
242                 "(example: ctdl.foo.org)\n"
243                 "--> ");
244         getz(remote_host);
245
246         while (IsEmptyStr(remote_user)) {
247                 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
248                         "(usually root)\n--> ",
249                         remote_host);
250                 getz(remote_user);
251         }
252
253         printf("\nEstablishing an SSH connection to the source system...\n\n");
254         sprintf(socket_path, "/tmp/ctdlmigrate.XXXXXX");
255         mktemp(socket_path);
256         unlink(socket_path);
257         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
258         cmdexit = system(cmd);
259         printf("\n");
260         if (cmdexit != 0) {
261                 printf("This program was unable to establish an SSH session to the source system.\n\n");
262                 exit(cmdexit);
263         }
264
265         printf("\nTesting a command over the connection...\n\n");
266         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
267                 socket_path, remote_user, remote_host);
268         cmdexit = system(cmd);
269         printf("\n");
270         if (cmdexit != 0) {
271                 printf("Remote commands are not succeeding.\n\n");
272                 exit(cmdexit);
273         }
274
275         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
276         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
277         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
278                 socket_path, remote_user, remote_host, remote_sendcommand);
279         cmdexit = system(cmd);
280         if (cmdexit != 0) {
281                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
282                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
283                         socket_path, remote_user, remote_host, remote_sendcommand);
284                 cmdexit = system(cmd);
285         }
286         if (cmdexit != 0) {
287                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
288                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
289                         "(example: /opt/foo/citadel)\n"
290                         "--> ", remote_host);
291                 getz(buf);
292                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
293                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
294                         socket_path, remote_user, remote_host, remote_sendcommand);
295                 cmdexit = system(cmd);
296         }
297         printf("\n");
298         if (cmdexit != 0) {
299                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
300                 exit(cmdexit);
301         }
302
303         printf("\033[2J\n");
304         printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
305
306         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
307         sourcefp = popen(cmd, "r");
308         if (!sourcefp) {
309                 printf("\n%s\n\n", strerror(errno));
310                 exit(2);
311         }
312
313         serv_puts(local_admin_socket, "MIGR import");
314         serv_gets(local_admin_socket, buf);
315         if (buf[0] != '4') {
316                 printf("\n%s\n", buf);
317                 exit(3);
318         }
319
320         char *ptr;
321         time_t time_started = time(NULL);
322         time_t last_update = time(NULL);
323         while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
324                 ptr = strchr(buf, '\n');
325                 if (ptr) *ptr = 0;
326                 ++linecount;
327                 if (!strncasecmp(buf, "<progress>", 10)) {
328                         printf("\033[11;0HPercent complete: \033[32m%d\033[0m\n", atoi(&buf[10]));
329                 }
330                 if (time(NULL) != last_update) {
331                         last_update = time(NULL);
332                         printf("\033[10;0H  Lines received: \033[32m%d\033[0m\n", linecount);
333                 }
334                 serv_puts(local_admin_socket, buf);
335         }
336
337         serv_puts(local_admin_socket, "000");
338
339         // FIXME restart the local server now
340
341         pclose(sourcefp);
342         printf("\nShutting down the socket connection...\n\n");
343         snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s", socket_path, remote_user, remote_host);
344         system(cmd);
345         unlink(socket_path);
346         exit(0);
347 }