19539ba37d13b2c46b6ed5eafa3bf626595f6142
[citadel.git] / citadel / utils / ctdlmigrate.c
1 // Across-the-wire migration utility for Citadel
2 //
3 // Copyright (c) 2009-2022 citadel.org
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <fcntl.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <sys/utsname.h>
17 #include <sys/wait.h>
18 #include <signal.h>
19 #include <netdb.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <pwd.h>
23 #include <time.h>
24 #include <readline/readline.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <libcitadel.h>
28 #include "../server/citadel.h"
29 #include "axdefs.h"
30 #include "../server/sysdep.h"
31 #include "../server/config.h"
32 #include "../server/citadel_dirs.h"
33
34
35 // support for getz() -- (globals would not be appropriate in a multithreaded program)
36 static int gmaxlen = 0;
37 static char *gdeftext = NULL;
38
39
40 // support function for getz()
41 static int limit_rl(FILE *dummy) {
42         if (rl_end > gmaxlen) {
43                 return '\b';
44         }
45         return rl_getc(dummy);
46 }
47
48
49 // support function for getz()
50 static int getz_deftext(void) {
51         if (gdeftext) {
52                 rl_insert_text(gdeftext);
53                 gdeftext = NULL;
54                 rl_startup_hook = NULL;
55         }
56         return 0;
57 }
58
59
60 // Replacement for gets() that uses libreadline.
61 void getz(char *buf, int maxlen, char *default_value, char *prompt) {
62         rl_startup_hook = getz_deftext;
63         rl_getc_function = limit_rl;
64         gmaxlen = maxlen;
65         gdeftext = default_value;
66         strcpy(buf, readline(prompt));
67 }
68
69
70 // Exit from the program while displaying an error code
71 void ctdlmigrate_exit(int cmdexit) {
72         printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
73         exit(cmdexit);
74 }
75
76
77 // Connect to a Citadel on a remote host using a TCP/IP socket
78 static int tcp_connectsock(char *host, char *service) {
79         struct in6_addr serveraddr;
80         struct addrinfo hints;
81         struct addrinfo *res = NULL;
82         struct addrinfo *ai = NULL;
83         int rc = (-1);
84         int sock = (-1);
85
86         if ((host == NULL) || IsEmptyStr(host)) {
87                 return(-1);
88         }
89         if ((service == NULL) || IsEmptyStr(service)) {
90                 return(-1);
91         }
92
93         memset(&hints, 0x00, sizeof(hints));
94         hints.ai_flags = AI_NUMERICSERV;
95         hints.ai_family = AF_UNSPEC;
96         hints.ai_socktype = SOCK_STREAM;
97
98         // Handle numeric IPv4 and IPv6 addresses
99         rc = inet_pton(AF_INET, host, &serveraddr);
100         if (rc == 1) {                                  // dotted quad
101                 hints.ai_family = AF_INET;
102                 hints.ai_flags |= AI_NUMERICHOST;
103         }
104         else {
105                 rc = inet_pton(AF_INET6, host, &serveraddr);
106                 if (rc == 1) {                          // IPv6 address
107                         hints.ai_family = AF_INET6;
108                         hints.ai_flags |= AI_NUMERICHOST;
109                 }
110         }
111
112         // Begin the connection process
113         rc = getaddrinfo(host, service, &hints, &res);
114         if (rc != 0) {
115                 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
116                 return (-1);
117         }
118
119         // Try all available addresses until we connect to one or until we run out.
120         for (ai = res; ai != NULL; ai = ai->ai_next) {
121                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
122                 if (sock < 0) {
123                         fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
124                         return (-1);
125                 }
126
127                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
128                 if (rc >= 0) {
129                         return (sock);  // Connected!
130                 }
131                 else {
132                         fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
133                         close(sock);    // Failed.  Close the socket to avoid fd leak!
134                 }
135         }
136         return (-1);
137 }
138
139
140 // Connect to a Citadel on a remote host using a unix domaion socket
141 int uds_connectsock(char *sockpath) {
142         int s;
143         struct sockaddr_un addr;
144
145         memset(&addr, 0, sizeof(addr));
146         addr.sun_family = AF_UNIX;
147         strcpy(addr.sun_path, sockpath);
148
149         s = socket(AF_UNIX, SOCK_STREAM, 0);
150         if (s < 0) {
151                 fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
152                 return(-1);
153         }
154
155         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
156                 fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
157                 close(s);
158                 return(-1);
159         }
160
161         return s;
162 }
163
164
165 // input binary data from socket
166 void serv_read(int serv_sock, char *buf, int bytes) {
167         int len = 0;
168         int rlen = 0;
169
170         while (len < bytes) {
171                 rlen = read(serv_sock, &buf[len], bytes - len);
172                 if (rlen < 1) {
173                         return;
174                 }
175                 len = len + rlen;
176         }
177 }
178
179
180 // send binary to server
181 void serv_write(int serv_sock, char *buf, int nbytes) {
182         int bytes_written = 0;
183         int retval;
184         while (bytes_written < nbytes) {
185                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
186                 if (retval < 1) {
187                         return;
188                 }
189                 bytes_written = bytes_written + retval;
190         }
191 }
192
193
194 // input string from socket - implemented in terms of serv_read()
195 void serv_gets(int serv_sock, char *buf) {
196         int i;
197
198         // Read one character at a time.
199         for (i = 0;; i++) {
200                 serv_read(serv_sock, &buf[i], 1);
201                 if (buf[i] == '\n' || i == (SIZ-1))
202                         break;
203         }
204
205         // If we got a long line, discard characters until the newline.
206         if (i == (SIZ-1)) {
207                 while (buf[i] != '\n') {
208                         serv_read(serv_sock, &buf[i], 1);
209                 }
210         }
211
212         // Strip all trailing nonprintables (crlf)
213         buf[i] = 0;
214 }
215
216
217 // send line to server - implemented in terms of serv_write()
218 void serv_puts(int serv_sock, char *buf) {
219         serv_write(serv_sock, buf, strlen(buf));
220         serv_write(serv_sock, "\n", 1);
221 }
222
223
224 // send formatted printable data to the server
225 void serv_printf(int serv_sock, const char *format, ...) {   
226         va_list arg_ptr;   
227         char buf[1024];
228    
229         va_start(arg_ptr, format);   
230         if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
231                 buf[sizeof buf - 2] = '\n';
232         serv_write(serv_sock, buf, strlen(buf)); 
233         va_end(arg_ptr);
234 }
235
236
237 // You know what main() does.  If you don't, you shouldn't be trying to understand this program.
238 int main(int argc, char *argv[]) {
239         char ctdldir[PATH_MAX]=CTDLDIR;
240         char yesno[2];
241         int cmdexit = 0;                                // when something fails, set cmdexit to nonzero, and skip to the end
242         char buf[PATH_MAX];
243
244         char remote_user[128];
245         char remote_host[128];
246         char remote_pass[128];
247         char *remote_port = "504";
248
249         int linecount = 0;
250         int a;
251         int remote_server_socket = (-1);
252         int local_admin_socket = (-1);
253         int progress = 0;
254
255         // Parse command line
256         while ((a = getopt(argc, argv, "h:")) != EOF) {
257                 switch (a) {
258                 case 'h':
259                         strcpy(ctdldir, optarg);
260                         break;
261                 default:
262                         fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
263                         return(1);
264                 }
265         }
266
267         if (chdir(ctdldir) != 0) {
268                 fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
269                 exit(errno);
270         }
271
272         signal(SIGINT, ctdlmigrate_exit);
273         signal(SIGQUIT, ctdlmigrate_exit);
274         signal(SIGTERM, ctdlmigrate_exit);
275         signal(SIGSEGV, ctdlmigrate_exit);
276         signal(SIGHUP, ctdlmigrate_exit);
277         signal(SIGPIPE, ctdlmigrate_exit);
278
279         printf( "\033[2J\033[H\n"
280                 "          \033[32m╔═══════════════════════════════════════════════╗\n"
281                 "          ║                                               ║\n"
282                 "          ║    \033[33mCitadel over-the-wire migration utility    \033[32m║\n"
283                 "          ║                                               ║\n"
284                 "          ╚═══════════════════════════════════════════════╝\033[0m\n"
285                 "\n"
286                 "This utility is designed to migrate your Citadel installation\n"
287                 "to a new host system via a network connection, without disturbing\n"
288                 "the source system.  The target may be a different CPU architecture\n"
289                 "and/or operating system.  The source system should be running\n"
290                 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
291                 "either the same version or a newer version.\n"
292                 "\n"
293                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
294                 "on this system will be ERASED.  Your target system will be on this\n"
295                 "host in %s.\n"
296                 "\n",
297                 EXPORT_REV_MIN, ctdldir
298         );
299
300         getz(yesno, 1, NULL, "Do you wish to continue? ");
301         puts(yesno);
302         if (tolower(yesno[0]) != 'y') {
303                 cmdexit = 101;
304         }
305
306         if (!cmdexit) {
307                 printf( "\033[2J\033[H\n"
308                         "\033[32m╔═══════════════════════════════════════════════╗\n"
309                         "║                                               ║\n"
310                         "║       \033[33mValidate source and target systems\033[32m      ║\n"
311                         "║                                               ║\n"
312                         "╚═══════════════════════════════════════════════╝\033[0m\n"
313                         "\n\n");
314         
315                 printf("First we must validate that the local target system is running\n");
316                 printf("and ready to receive data.\n");
317                 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
318                 local_admin_socket = uds_connectsock("citadel-admin.socket");
319                 if (local_admin_socket < 0) {
320                         cmdexit = 102;
321                 }
322         }
323
324         if (!cmdexit) {
325                 serv_gets(local_admin_socket, buf);
326                 puts(buf);
327                 if (buf[0] != '2') {
328                         cmdexit = 103;
329                 }
330         }
331
332         if (!cmdexit) {
333                 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
334                 serv_gets(local_admin_socket, buf);
335                 puts(buf);
336                 if (buf[0] != '2') {
337                         cmdexit = 104;
338                 }
339         }
340
341         if (!cmdexit) {
342                 printf("\n");
343                 printf("OK, this side is ready to go.  Now we must connect to the source system.\n\n");
344
345                 getz(remote_host, sizeof remote_host, NULL,     "Enter the name or IP address of the source system: ");
346                 getz(remote_user, sizeof remote_user, "admin",  "  Enter the user name of an administrator account: ");
347                 getz(remote_pass, sizeof remote_pass, NULL,     "              Enter the password for this account: ");
348
349                 remote_server_socket = tcp_connectsock(remote_host, remote_port);
350                 if (remote_server_socket < 0) {
351                         cmdexit = 105;
352                 }
353         }
354
355         if (!cmdexit) {
356                 serv_gets(remote_server_socket, buf);
357                 puts(buf);
358                 if (buf[0] != '2') {
359                         cmdexit = 106;
360                 }
361         }
362
363         if (!cmdexit) {
364                 serv_printf(remote_server_socket, "USER %s\n", remote_user);
365                 serv_gets(remote_server_socket, buf);
366                 puts(buf);
367                 if (buf[0] != '3') {
368                         cmdexit = 107;
369                 }
370         }
371
372         if (!cmdexit) {
373                 serv_printf(remote_server_socket, "PASS %s\n", remote_pass);
374                 serv_gets(remote_server_socket, buf);
375                 puts(buf);
376                 if (buf[0] != '2') {
377                         cmdexit = 108;
378                 }
379         }
380
381         if (!cmdexit) {
382                 printf( "\033[2J\033[H\n"
383                         "\033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
384                         "║                                                                   ║\n"
385                         "║ \033[33mMigrating from: %-50s\033[32m║\n"
386                         "║                                                                   ║\n"
387                         "╠═══════════════════════════════════════════════════════════════════╣\n"
388                         "║                                                                   ║\n"
389                         "║ Lines received: 0                           Percent complete: 0   ║\n"
390                         "║                                                                   ║\n"
391                         "╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
392                         "\n", remote_host
393                 );
394         }
395
396         if (!cmdexit) {
397                 serv_puts(remote_server_socket, "MIGR export");
398                 serv_gets(remote_server_socket, buf);
399                 if (buf[0] != '1') {
400                         printf("\n\033[31m%s\033[0m\n", buf);
401                         cmdexit = 109;
402                 }
403         }
404
405         if (!cmdexit) {
406                 serv_puts(local_admin_socket, "MIGR import");
407                 serv_gets(local_admin_socket, buf);
408                 if (buf[0] != '4') {
409                         printf("\n\033[31m%s\033[0m\n", buf);
410                         cmdexit = 110;
411                 }
412         }
413
414         if (!cmdexit) {
415                 char *ptr;
416                 time_t last_update = time(NULL);
417
418                 while (serv_gets(remote_server_socket, buf), strcmp(buf, "000")) {
419                         ptr = strchr(buf, '\n');
420                         if (ptr) *ptr = 0;      // remove the newline character
421                         ++linecount;
422                         if (!strncasecmp(buf, "<progress>", 10)) {
423                                 progress = atoi(&buf[10]);
424                                 printf("\033[8;65H\033[33m%d\033[0m\033[12;0H\n", progress);
425                         }
426                         if (time(NULL) != last_update) {
427                                 last_update = time(NULL);
428                                 printf("\033[8;19H\033[33m%d\033[0m\033[12;0H\n", linecount);
429                         }
430                         serv_puts(local_admin_socket, buf);
431                 }
432         
433                 serv_puts(local_admin_socket, "000");
434         }
435
436         if ( (cmdexit == 0) && (progress < 100) ) {
437                 printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
438                 cmdexit = 111;
439         }
440
441         if (!cmdexit) {
442                 printf("\033[36mMigration is complete.  Restarting the target server.\033[0m\n");
443                 serv_puts(local_admin_socket, "DOWN 1");
444                 serv_gets(local_admin_socket, buf);
445                 puts(buf);
446                 printf("\033[36mIt is recommended that you shut down the source server now.\033[0m\n");
447                 printf("\033[36mRemember to copy over your SSL keys and file libraries if appropriate.\033[0m\n");
448         }
449
450         close(remote_server_socket);
451         close(local_admin_socket);
452         ctdlmigrate_exit(cmdexit);
453 }