1 // Across-the-wire migration utility for Citadel
3 // Copyright (c) 2009-2022 citadel.org
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.
14 #include <sys/types.h>
16 #include <sys/utsname.h>
24 #include <readline/readline.h>
25 #include <sys/socket.h>
27 #include <libcitadel.h>
28 #include "../server/citadel.h"
30 #include "../server/sysdep.h"
31 #include "../server/config.h"
32 #include "../server/citadel_dirs.h"
35 // support for getz() -- (globals would not be appropriate in a multithreaded program)
36 static int gmaxlen = 0;
37 static char *gdeftext = NULL;
40 // support function for getz()
41 static int limit_rl(FILE *dummy) {
42 if (rl_end > gmaxlen) {
45 return rl_getc(dummy);
49 // support function for getz()
50 static int getz_deftext(void) {
52 rl_insert_text(gdeftext);
54 rl_startup_hook = NULL;
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;
65 gdeftext = default_value;
66 strcpy(buf, readline(prompt));
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);
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;
86 if ((host == NULL) || IsEmptyStr(host)) {
89 if ((service == NULL) || IsEmptyStr(service)) {
93 memset(&hints, 0x00, sizeof(hints));
94 hints.ai_flags = AI_NUMERICSERV;
95 hints.ai_family = AF_UNSPEC;
96 hints.ai_socktype = SOCK_STREAM;
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;
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;
112 // Begin the connection process
113 rc = getaddrinfo(host, service, &hints, &res);
115 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
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);
123 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
127 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
129 return (sock); // Connected!
132 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
133 close(sock); // Failed. Close the socket to avoid fd leak!
140 // Connect to a Citadel on a remote host using a unix domaion socket
141 int uds_connectsock(char *sockpath) {
143 struct sockaddr_un addr;
145 memset(&addr, 0, sizeof(addr));
146 addr.sun_family = AF_UNIX;
147 strcpy(addr.sun_path, sockpath);
149 s = socket(AF_UNIX, SOCK_STREAM, 0);
151 fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
155 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
156 fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
165 // input binary data from socket
166 void serv_read(int serv_sock, char *buf, int bytes) {
170 while (len < bytes) {
171 rlen = read(serv_sock, &buf[len], bytes - len);
180 // send binary to server
181 void serv_write(int serv_sock, char *buf, int nbytes) {
182 int bytes_written = 0;
184 while (bytes_written < nbytes) {
185 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
189 bytes_written = bytes_written + retval;
194 // input string from socket - implemented in terms of serv_read()
195 void serv_gets(int serv_sock, char *buf) {
198 // Read one character at a time.
200 serv_read(serv_sock, &buf[i], 1);
201 if (buf[i] == '\n' || i == (SIZ-1))
205 // If we got a long line, discard characters until the newline.
207 while (buf[i] != '\n') {
208 serv_read(serv_sock, &buf[i], 1);
212 // Strip all trailing nonprintables (crlf)
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);
224 // send formatted printable data to the server
225 void serv_printf(int serv_sock, const char *format, ...) {
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));
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;
241 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
244 char remote_user[128];
245 char remote_host[128];
246 char remote_pass[128];
247 char *remote_port = "504";
251 int remote_server_socket = (-1);
252 int local_admin_socket = (-1);
255 // Parse command line
256 while ((a = getopt(argc, argv, "h:")) != EOF) {
259 strcpy(ctdldir, optarg);
262 fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
267 if (chdir(ctdldir) != 0) {
268 fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
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);
279 printf( "\033[2J\033[H\n"
280 " \033[32m╔═══════════════════════════════════════════════╗\n"
282 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
284 " ╚═══════════════════════════════════════════════╝\033[0m\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"
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"
297 EXPORT_REV_MIN, ctdldir
300 getz(yesno, 1, NULL, "Do you wish to continue? ");
302 if (tolower(yesno[0]) != 'y') {
307 printf( "\033[2J\033[H\n"
308 "\033[32m╔═══════════════════════════════════════════════╗\n"
310 "║ \033[33mValidate source and target systems\033[32m ║\n"
312 "╚═══════════════════════════════════════════════╝\033[0m\n"
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) {
325 serv_gets(local_admin_socket, buf);
333 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
334 serv_gets(local_admin_socket, buf);
343 printf("OK, this side is ready to go. Now we must connect to the source system.\n\n");
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: ");
349 remote_server_socket = tcp_connectsock(remote_host, remote_port);
350 if (remote_server_socket < 0) {
356 serv_gets(remote_server_socket, buf);
364 serv_printf(remote_server_socket, "USER %s\n", remote_user);
365 serv_gets(remote_server_socket, buf);
373 serv_printf(remote_server_socket, "PASS %s\n", remote_pass);
374 serv_gets(remote_server_socket, buf);
382 printf( "\033[2J\033[H\n"
383 "\033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
385 "║ \033[33mMigrating from: %-50s\033[32m║\n"
387 "╠═══════════════════════════════════════════════════════════════════╣\n"
389 "║ Lines received: 0 Percent complete: 0 ║\n"
391 "╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
397 serv_puts(remote_server_socket, "MIGR export");
398 serv_gets(remote_server_socket, buf);
400 printf("\n\033[31m%s\033[0m\n", buf);
406 serv_puts(local_admin_socket, "MIGR import");
407 serv_gets(local_admin_socket, buf);
409 printf("\n\033[31m%s\033[0m\n", buf);
416 time_t last_update = time(NULL);
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
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);
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);
430 serv_puts(local_admin_socket, buf);
433 serv_puts(local_admin_socket, "000");
436 if ( (cmdexit == 0) && (progress < 100) ) {
437 printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
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);
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");
450 close(remote_server_socket);
451 close(local_admin_socket);
452 ctdlmigrate_exit(cmdexit);