2 * Across-the-wire migration utility for Citadel
4 * Copyright (c) 2009-2021 citadel.org
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.
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.
21 #include <sys/types.h>
23 #include <sys/utsname.h>
31 #include <readline/readline.h>
32 #include <sys/socket.h>
34 #include <libcitadel.h>
39 #include "citadel_dirs.h"
43 // yeah, this wouldn't work in a multithreaded program.
44 static int gmaxlen = 0;
45 static char *gdeftext = NULL;
47 static int limit_rl(FILE *dummy) {
48 if (rl_end > gmaxlen) {
51 return rl_getc(dummy);
55 static int getz_deftext(void) {
57 rl_insert_text(gdeftext);
59 rl_startup_hook = NULL;
66 * Replacement for gets() that doesn't throw a compiler warning.
67 * We're only using it for some simple prompts, so we don't need
68 * to worry about attackers exploiting it.
70 void getz(char *buf, int maxlen, char *default_value, char *prompt) {
71 rl_startup_hook = getz_deftext;
72 rl_getc_function = limit_rl;
74 gdeftext = default_value;
75 strcpy(buf, readline(prompt));
79 void ctdlmigrate_exit(int cmdexit) {
80 printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
86 * Connect to a Citadel on a remote host using a TCP/IP socket
88 static int tcp_connectsock(char *host, char *service) {
89 struct in6_addr serveraddr;
90 struct addrinfo hints;
91 struct addrinfo *res = NULL;
92 struct addrinfo *ai = NULL;
96 if ((host == NULL) || IsEmptyStr(host)) {
99 if ((service == NULL) || IsEmptyStr(service)) {
103 memset(&hints, 0x00, sizeof(hints));
104 hints.ai_flags = AI_NUMERICSERV;
105 hints.ai_family = AF_UNSPEC;
106 hints.ai_socktype = SOCK_STREAM;
109 * Handle numeric IPv4 and IPv6 addresses
111 rc = inet_pton(AF_INET, host, &serveraddr);
112 if (rc == 1) { /* dotted quad */
113 hints.ai_family = AF_INET;
114 hints.ai_flags |= AI_NUMERICHOST;
116 rc = inet_pton(AF_INET6, host, &serveraddr);
117 if (rc == 1) { /* IPv6 address */
118 hints.ai_family = AF_INET6;
119 hints.ai_flags |= AI_NUMERICHOST;
123 /* Begin the connection process */
125 rc = getaddrinfo(host, service, &hints, &res);
127 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
132 * Try all available addresses until we connect to one or until we run out.
134 for (ai = res; ai != NULL; ai = ai->ai_next) {
135 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
137 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
141 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
143 return (sock); /* Connected! */
146 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
147 close(sock); /* Failed. Close the socket to avoid fd leak! */
154 int uds_connectsock(char *sockpath) {
156 struct sockaddr_un addr;
158 memset(&addr, 0, sizeof(addr));
159 addr.sun_family = AF_UNIX;
160 strcpy(addr.sun_path, sockpath);
162 s = socket(AF_UNIX, SOCK_STREAM, 0);
164 fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
168 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
169 fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
179 * input binary data from socket
181 void serv_read(int serv_sock, char *buf, int bytes) {
185 while (len < bytes) {
186 rlen = read(serv_sock, &buf[len], bytes - len);
196 * send binary to server
198 void serv_write(int serv_sock, char *buf, int nbytes) {
199 int bytes_written = 0;
201 while (bytes_written < nbytes) {
202 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
206 bytes_written = bytes_written + retval;
212 * input string from socket - implemented in terms of serv_read()
214 void serv_gets(int serv_sock, char *buf) {
217 /* Read one character at a time.
220 serv_read(serv_sock, &buf[i], 1);
221 if (buf[i] == '\n' || i == (SIZ-1))
225 /* If we got a long line, discard characters until the newline.
228 while (buf[i] != '\n') {
229 serv_read(serv_sock, &buf[i], 1);
233 /* Strip all trailing nonprintables (crlf)
240 * send line to server - implemented in terms of serv_write()
242 void serv_puts(int serv_sock, char *buf) {
243 serv_write(serv_sock, buf, strlen(buf));
244 serv_write(serv_sock, "\n", 1);
249 * serv_printf() Send formatted printable data to the server
251 void serv_printf(int serv_sock, const char *format, ...) {
255 va_start(arg_ptr, format);
256 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
257 buf[sizeof buf - 2] = '\n';
258 serv_write(serv_sock, buf, strlen(buf));
263 int main(int argc, char *argv[]) {
264 char ctdldir[PATH_MAX]=CTDLDIR;
266 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
269 char remote_user[128];
270 char remote_host[128];
271 char remote_pass[128];
272 char *remote_port = "504";
276 int remote_server_socket = (-1);
277 int local_admin_socket = (-1);
280 /* Parse command line */
281 while ((a = getopt(argc, argv, "h:")) != EOF) {
284 strcpy(ctdldir, optarg);
287 fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
292 if (chdir(ctdldir) != 0) {
293 fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
297 signal(SIGINT, ctdlmigrate_exit);
298 signal(SIGQUIT, ctdlmigrate_exit);
299 signal(SIGTERM, ctdlmigrate_exit);
300 signal(SIGSEGV, ctdlmigrate_exit);
301 signal(SIGHUP, ctdlmigrate_exit);
302 signal(SIGPIPE, ctdlmigrate_exit);
304 printf( "\033[2J\033[H\n"
305 " \033[32m╔═══════════════════════════════════════════════╗\n"
307 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
309 " ╚═══════════════════════════════════════════════╝\033[0m\n"
311 "This utility is designed to migrate your Citadel installation\n"
312 "to a new host system via a network connection, without disturbing\n"
313 "the source system. The target may be a different CPU architecture\n"
314 "and/or operating system. The source system should be running\n"
315 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
316 "either the same version or a newer version. You will also need\n"
317 "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\n"
319 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
320 "on this system will be ERASED. Your target system will be on this\n"
323 EXPORT_REV_MIN, ctdldir
326 getz(yesno, 1, NULL, "Do you wish to continue? ");
328 if (tolower(yesno[0]) != 'y') {
333 printf( "\033[2J\033[H\n"
334 " \033[32m╔═══════════════════════════════════════════════╗\n"
336 " ║ \033[33mValidate source and target systems\033[32m ║\n"
338 " ╚═══════════════════════════════════════════════╝\033[0m\n"
341 printf("First we must validate that the local target system is running and ready to receive data.\n");
342 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
343 local_admin_socket = uds_connectsock("citadel-admin.socket");
344 if (local_admin_socket < 0) {
350 serv_gets(local_admin_socket, buf);
358 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
359 serv_gets(local_admin_socket, buf);
368 printf("OK, this side is ready to go. Now we must connect to the source system.\n\n");
370 getz(remote_host, sizeof remote_host, NULL, "Enter the name or IP address of the source system: ");
371 getz(remote_user, sizeof remote_user, "admin", " Enter the user name of an administrator account: ");
372 getz(remote_pass, sizeof remote_pass, NULL, " Enter the password for this account: ");
374 remote_server_socket = tcp_connectsock(remote_host, remote_port);
375 if (remote_server_socket < 0) {
381 serv_gets(remote_server_socket, buf);
389 serv_printf(remote_server_socket, "USER %s\n", remote_user);
390 serv_gets(remote_server_socket, buf);
398 serv_printf(remote_server_socket, "PASS %s\n", remote_pass);
399 serv_gets(remote_server_socket, buf);
407 printf( "\033[2J\033[H\n"
408 " \033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
410 " ║ \033[33mMigrating from: %-50s\033[32m║\n"
412 " ╠═══════════════════════════════════════════════════════════════════╣\n"
414 " ║ Lines received: 0 Percent complete: 0 ║\n"
416 " ╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
422 serv_puts(remote_server_socket, "MIGR export");
423 serv_gets(remote_server_socket, buf);
425 printf("\n\033[31m%s\033[0m\n", buf);
431 serv_puts(local_admin_socket, "MIGR import");
432 serv_gets(local_admin_socket, buf);
434 printf("\n\033[31m%s\033[0m\n", buf);
441 time_t last_update = time(NULL);
443 while (serv_gets(remote_server_socket, buf), strcmp(buf, "000")) {
444 ptr = strchr(buf, '\n');
445 if (ptr) *ptr = 0; // remove the newline character
447 if (!strncasecmp(buf, "<progress>", 10)) {
448 progress = atoi(&buf[10]);
449 printf("\033[8;75H\033[33m%d\033[0m\033[20;0H\n", progress);
451 if (time(NULL) != last_update) {
452 last_update = time(NULL);
453 printf("\033[8;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
455 serv_puts(local_admin_socket, buf);
458 serv_puts(local_admin_socket, "000");
461 if ( (cmdexit == 0) && (progress < 100) ) {
462 printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
463 ctdlmigrate_exit(86);
466 // FIXME restart the local server now
468 ctdlmigrate_exit(cmdexit);