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 // These variables are used by both main() and ctdlmigrate_exit()
80 // They are global so that ctdlmigrate_exit can be called from a signal handler
81 FILE *sourcefp = NULL;
82 char socket_path[PATH_MAX];
85 void ctdlmigrate_exit(int cmdexit) {
87 printf("Closing the data connection from the source system...\n");
92 printf("Shutting down the SSH session...\n");
93 kill(sshpid, SIGKILL);
95 printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
100 int uds_connectsock(char *sockpath) {
102 struct sockaddr_un addr;
104 memset(&addr, 0, sizeof(addr));
105 addr.sun_family = AF_UNIX;
106 strcpy(addr.sun_path, sockpath);
108 s = socket(AF_UNIX, SOCK_STREAM, 0);
110 fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
114 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
115 fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
125 * input binary data from socket
127 void serv_read(int serv_sock, char *buf, int bytes) {
131 while (len < bytes) {
132 rlen = read(serv_sock, &buf[len], bytes - len);
142 * send binary to server
144 void serv_write(int serv_sock, char *buf, int nbytes) {
145 int bytes_written = 0;
147 while (bytes_written < nbytes) {
148 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
152 bytes_written = bytes_written + retval;
158 * input string from socket - implemented in terms of serv_read()
160 void serv_gets(int serv_sock, char *buf) {
163 /* Read one character at a time.
166 serv_read(serv_sock, &buf[i], 1);
167 if (buf[i] == '\n' || i == (SIZ-1))
171 /* If we got a long line, discard characters until the newline.
174 while (buf[i] != '\n') {
175 serv_read(serv_sock, &buf[i], 1);
179 /* Strip all trailing nonprintables (crlf)
186 * send line to server - implemented in terms of serv_write()
188 void serv_puts(int serv_sock, char *buf) {
189 serv_write(serv_sock, buf, strlen(buf));
190 serv_write(serv_sock, "\n", 1);
194 int main(int argc, char *argv[]) {
195 char ctdldir[PATH_MAX]=CTDLDIR;
197 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
200 char remote_user[SIZ];
201 char remote_host[SIZ];
202 char remote_sendcommand[PATH_MAX];
205 int local_admin_socket = (-1);
208 /* Parse command line */
209 while ((a = getopt(argc, argv, "h:")) != EOF) {
212 strcpy(ctdldir, optarg);
215 fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
220 if (chdir(ctdldir) != 0) {
221 fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
225 signal(SIGINT, ctdlmigrate_exit);
226 signal(SIGQUIT, ctdlmigrate_exit);
227 signal(SIGTERM, ctdlmigrate_exit);
228 signal(SIGSEGV, ctdlmigrate_exit);
229 signal(SIGHUP, ctdlmigrate_exit);
230 signal(SIGPIPE, ctdlmigrate_exit);
232 printf( "\033[2J\033[H\n"
233 " \033[32m╔═══════════════════════════════════════════════╗\n"
235 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
237 " ╚═══════════════════════════════════════════════╝\033[0m\n"
239 "This utility is designed to migrate your Citadel installation\n"
240 "to a new host system via a network connection, without disturbing\n"
241 "the source system. The target may be a different CPU architecture\n"
242 "and/or operating system. The source system should be running\n"
243 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
244 "either the same version or a newer version. You will also need\n"
245 "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\n"
247 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
248 "on this system will be ERASED. Your target system will be on this\n"
251 EXPORT_REV_MIN, ctdldir
254 getz(yesno, 1, NULL, "Do you wish to continue? ");
256 if (tolower(yesno[0]) != 'y') {
262 printf( "\033[2J\033[H\n"
263 " \033[32m╔═══════════════════════════════════════════════╗\n"
265 " ║ \033[33mValidate source and target systems\033[32m ║\n"
267 " ╚═══════════════════════════════════════════════╝\033[0m\n"
270 printf("First we must validate that the local target system is running and ready to receive data.\n");
271 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
272 local_admin_socket = uds_connectsock("citadel-admin.socket");
274 serv_gets(local_admin_socket, buf);
282 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
283 serv_gets(local_admin_socket, buf);
292 printf("OK, this side is ready to go. Now we must connect to the source system.\n\n");
293 printf("Enter the host name or IP address of the source system\n");
294 printf("(example: ctdl.foo.org)\n");
295 getz(remote_host, sizeof remote_host, NULL, "--> ");
297 while (IsEmptyStr(remote_user)) {
299 printf("Enter the name of a user on %s who has full access to Citadel files.\n", remote_host);
300 getz(remote_user, sizeof remote_user, "root", "--> ");
304 printf("Establishing an SSH connection to the source system...\n");
305 sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
308 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
311 printf("%s\n", strerror(errno));
314 else if (sshpid == 0) {
315 execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
318 else { // Wait for SSH to go into the background
319 waitpid(sshpid, &cmdexit, 0);
324 printf("\nTesting a command over the connection...\n\n");
325 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
326 socket_path, remote_user, remote_host);
327 cmdexit = system(cmd);
330 printf("\033[31mRemote commands are not succeeding.\033[0m\n\n");
335 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
336 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
337 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
338 cmdexit = system(cmd);
341 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
342 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
343 cmdexit = system(cmd);
348 printf("Unable to locate Citadel programs on the remote system. Please enter\n"
349 "the name of the directory on %s which contains the 'sendcommand' program.\n"
350 "(example: /opt/foo/citadel)\n", remote_host);
351 getz(remote_host, sizeof remote_host, "/usr/local/citadel", "--> ");
352 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
353 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
354 cmdexit = system(cmd);
356 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
362 printf( "\033[2J\033[H\n"
363 " \033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
365 " ║ \033[33mMigrating from: %-50s\033[32m║\n"
367 " ╠═══════════════════════════════════════════════════════════════════╣\n"
369 " ║ Lines received: 0 Percent complete: 0 ║\n"
371 " ╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
375 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
376 sourcefp = popen(cmd, "r");
379 printf("\n%s\n\n", strerror(errno));
384 serv_puts(local_admin_socket, "MIGR import");
385 serv_gets(local_admin_socket, buf);
387 printf("\n\033[31m%s\033[0m\n", buf);
394 time_t last_update = time(NULL);
395 while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
396 ptr = strchr(buf, '\n');
397 if (ptr) *ptr = 0; // remove the newline character
399 if (!strncasecmp(buf, "<progress>", 10)) {
400 progress = atoi(&buf[10]);
401 printf("\033[8;75H\033[33m%d\033[0m\033[20;0H\n", progress);
403 if (time(NULL) != last_update) {
404 last_update = time(NULL);
405 printf("\033[8;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
407 serv_puts(local_admin_socket, buf);
410 serv_puts(local_admin_socket, "000");
413 if ( (cmdexit == 0) && (progress < 100) ) {
414 printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
415 ctdlmigrate_exit(86);
418 // FIXME restart the local server now
420 ctdlmigrate_exit(cmdexit);