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