1 // Across-the-wire migration utility for Citadel
3 // Copyright (c) 2009-2021 citadel.org
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.
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.
19 #include <sys/types.h>
21 #include <sys/utsname.h>
29 #include <readline/readline.h>
30 #include <sys/socket.h>
32 #include <libcitadel.h>
37 #include "citadel_dirs.h"
40 // support for getz() -- (globals would not be appropriate in a multithreaded program)
41 static int gmaxlen = 0;
42 static char *gdeftext = NULL;
45 // support function for getz()
46 static int limit_rl(FILE *dummy) {
47 if (rl_end > gmaxlen) {
50 return rl_getc(dummy);
54 // support function for getz()
55 static int getz_deftext(void) {
57 rl_insert_text(gdeftext);
59 rl_startup_hook = NULL;
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;
70 gdeftext = default_value;
71 strcpy(buf, readline(prompt));
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);
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;
91 if ((host == NULL) || IsEmptyStr(host)) {
94 if ((service == NULL) || IsEmptyStr(service)) {
98 memset(&hints, 0x00, sizeof(hints));
99 hints.ai_flags = AI_NUMERICSERV;
100 hints.ai_family = AF_UNSPEC;
101 hints.ai_socktype = SOCK_STREAM;
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;
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;
116 // Begin the connection process
117 rc = getaddrinfo(host, service, &hints, &res);
119 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
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);
127 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
131 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
133 return (sock); // Connected!
136 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
137 close(sock); // Failed. Close the socket to avoid fd leak!
144 // Connect to a Citadel on a remote host using a unix domaion socket
145 int uds_connectsock(char *sockpath) {
147 struct sockaddr_un addr;
149 memset(&addr, 0, sizeof(addr));
150 addr.sun_family = AF_UNIX;
151 strcpy(addr.sun_path, sockpath);
153 s = socket(AF_UNIX, SOCK_STREAM, 0);
155 fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
159 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
160 fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
169 // input binary data from socket
170 void serv_read(int serv_sock, char *buf, int bytes) {
174 while (len < bytes) {
175 rlen = read(serv_sock, &buf[len], bytes - len);
184 // send binary to server
185 void serv_write(int serv_sock, char *buf, int nbytes) {
186 int bytes_written = 0;
188 while (bytes_written < nbytes) {
189 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
193 bytes_written = bytes_written + retval;
198 // input string from socket - implemented in terms of serv_read()
199 void serv_gets(int serv_sock, char *buf) {
202 // Read one character at a time.
204 serv_read(serv_sock, &buf[i], 1);
205 if (buf[i] == '\n' || i == (SIZ-1))
209 // If we got a long line, discard characters until the newline.
211 while (buf[i] != '\n') {
212 serv_read(serv_sock, &buf[i], 1);
216 // Strip all trailing nonprintables (crlf)
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);
228 // send formatted printable data to the server
229 void serv_printf(int serv_sock, const char *format, ...) {
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));
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;
245 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
248 char remote_user[128];
249 char remote_host[128];
250 char remote_pass[128];
251 char *remote_port = "504";
255 int remote_server_socket = (-1);
256 int local_admin_socket = (-1);
259 // Parse command line
260 while ((a = getopt(argc, argv, "h:")) != EOF) {
263 strcpy(ctdldir, optarg);
266 fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
271 if (chdir(ctdldir) != 0) {
272 fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
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);
283 printf( "\033[2J\033[H\n"
284 " \033[32m╔═══════════════════════════════════════════════╗\n"
286 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
288 " ╚═══════════════════════════════════════════════╝\033[0m\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"
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"
301 EXPORT_REV_MIN, ctdldir
304 getz(yesno, 1, NULL, "Do you wish to continue? ");
306 if (tolower(yesno[0]) != 'y') {
311 printf( "\033[2J\033[H\n"
312 "\033[32m╔═══════════════════════════════════════════════╗\n"
314 "║ \033[33mValidate source and target systems\033[32m ║\n"
316 "╚═══════════════════════════════════════════════╝\033[0m\n"
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) {
329 serv_gets(local_admin_socket, buf);
337 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
338 serv_gets(local_admin_socket, buf);
347 printf("OK, this side is ready to go. Now we must connect to the source system.\n\n");
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: ");
353 remote_server_socket = tcp_connectsock(remote_host, remote_port);
354 if (remote_server_socket < 0) {
360 serv_gets(remote_server_socket, buf);
368 serv_printf(remote_server_socket, "USER %s\n", remote_user);
369 serv_gets(remote_server_socket, buf);
377 serv_printf(remote_server_socket, "PASS %s\n", remote_pass);
378 serv_gets(remote_server_socket, buf);
386 printf( "\033[2J\033[H\n"
387 "\033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
389 "║ \033[33mMigrating from: %-50s\033[32m║\n"
391 "╠═══════════════════════════════════════════════════════════════════╣\n"
393 "║ Lines received: 0 Percent complete: 0 ║\n"
395 "╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
401 serv_puts(remote_server_socket, "MIGR export");
402 serv_gets(remote_server_socket, buf);
404 printf("\n\033[31m%s\033[0m\n", buf);
410 serv_puts(local_admin_socket, "MIGR import");
411 serv_gets(local_admin_socket, buf);
413 printf("\n\033[31m%s\033[0m\n", buf);
420 time_t last_update = time(NULL);
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
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);
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);
434 serv_puts(local_admin_socket, buf);
437 serv_puts(local_admin_socket, "000");
440 if ( (cmdexit == 0) && (progress < 100) ) {
441 printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
446 printf("\033[36mMigration is complete. Restarting the target server.\033[0m\n");
447 serv_puts(local_admin_socket, "DOWN 1");
448 serv_gets(local_admin_socket, buf);
450 printf("\033[36mIt is recommended that you shut down the source server now.\033[0m\n");
451 printf("\033[36mRemember to copy over your SSL keys and file libraries if appropriate.\033[0m\n");
454 close(remote_server_socket);
455 close(local_admin_socket);
456 ctdlmigrate_exit(cmdexit);