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