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 <sys/socket.h>
33 #include <libcitadel.h>
38 #include "citadel_dirs.h"
42 * Replacement for gets() that doesn't throw a compiler warning.
43 * We're only using it for some simple prompts, so we don't need
44 * to worry about attackers exploiting it.
46 void getz(char *buf) {
49 ptr = fgets(buf, SIZ, stdin);
55 ptr = strchr(buf, '\n');
60 // These variables are used by both main() and ctdlmigrate_exit()
61 // They are global so that ctdlmigrate_exit can be called from a signal handler
62 char socket_path[PATH_MAX];
65 void ctdlmigrate_exit(int cmdexit) {
68 printf("Shutting down the SSH session...\n");
69 kill(sshpid, SIGKILL);
71 printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
76 int uds_connectsock(char *sockpath) {
78 struct sockaddr_un addr;
80 memset(&addr, 0, sizeof(addr));
81 addr.sun_family = AF_UNIX;
82 strcpy(addr.sun_path, sockpath);
84 s = socket(AF_UNIX, SOCK_STREAM, 0);
86 fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
90 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
91 fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
101 * input binary data from socket
103 void serv_read(int serv_sock, char *buf, int bytes) {
107 while (len < bytes) {
108 rlen = read(serv_sock, &buf[len], bytes - len);
118 * send binary to server
120 void serv_write(int serv_sock, char *buf, int nbytes) {
121 int bytes_written = 0;
123 while (bytes_written < nbytes) {
124 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
128 bytes_written = bytes_written + retval;
134 * input string from socket - implemented in terms of serv_read()
136 void serv_gets(int serv_sock, char *buf) {
139 /* Read one character at a time.
142 serv_read(serv_sock, &buf[i], 1);
143 if (buf[i] == '\n' || i == (SIZ-1))
147 /* If we got a long line, discard characters until the newline.
150 while (buf[i] != '\n') {
151 serv_read(serv_sock, &buf[i], 1);
155 /* Strip all trailing nonprintables (crlf)
162 * send line to server - implemented in terms of serv_write()
164 void serv_puts(int serv_sock, char *buf) {
165 serv_write(serv_sock, buf, strlen(buf));
166 serv_write(serv_sock, "\n", 1);
170 int main(int argc, char *argv[]) {
171 char ctdldir[PATH_MAX]=CTDLDIR;
173 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
176 char remote_user[SIZ];
177 char remote_host[SIZ];
178 char remote_sendcommand[PATH_MAX];
179 FILE *sourcefp = NULL;
182 int local_admin_socket = (-1);
184 /* Parse command line */
185 while ((a = getopt(argc, argv, "h:")) != EOF) {
188 strcpy(ctdldir, optarg);
191 fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
196 if (chdir(ctdldir) != 0) {
197 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
201 signal(SIGINT, ctdlmigrate_exit);
202 signal(SIGQUIT, ctdlmigrate_exit);
203 signal(SIGTERM, ctdlmigrate_exit);
204 signal(SIGSEGV, ctdlmigrate_exit);
205 signal(SIGHUP, ctdlmigrate_exit);
206 signal(SIGPIPE, ctdlmigrate_exit);
208 printf( "\033[2J\033[H\n"
209 " \033[32m╔═══════════════════════════════════════════════╗\n"
211 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
213 " ╚═══════════════════════════════════════════════╝\033[0m\n"
215 "This utility is designed to migrate your Citadel installation\n"
216 "to a new host system via a network connection, without disturbing\n"
217 "the source system. The target may be a different CPU architecture\n"
218 "and/or operating system. The source system should be running\n"
219 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
220 "either the same version or a newer version. You will also need\n"
221 "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\n"
223 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
224 "on this system will be ERASED. Your target system will be on this\n"
227 "\033[33mDo you wish to continue? \033[0m"
229 EXPORT_REV_MIN, ctdldir
232 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
238 printf( "\033[2J\033[H\n"
239 " \033[32m╔═══════════════════════════════════════════════╗\n"
241 " ║ \033[33mValidate source and target systems\033[32m ║\n"
243 " ╚═══════════════════════════════════════════════╝\033[0m\n"
246 printf("First we must validate that the local target system is running and ready to receive data.\n");
247 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
248 local_admin_socket = uds_connectsock("citadel-admin.socket");
250 serv_gets(local_admin_socket, buf);
258 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
259 serv_gets(local_admin_socket, buf);
267 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
268 printf("Enter the host name or IP address of the source system\n"
269 "(example: ctdl.foo.org)\n"
273 while (IsEmptyStr(remote_user)) {
274 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
275 "(usually root)\n--> ",
280 printf("\nEstablishing an SSH connection to the source system...\n\n");
281 sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
284 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
287 printf("%s\n", strerror(errno));
290 else if (sshpid == 0) {
291 execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
294 else { // Wait for SSH to go into the background
295 waitpid(sshpid, &cmdexit, 0);
300 printf("\nTesting a command over the connection...\n\n");
301 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
302 socket_path, remote_user, remote_host);
303 cmdexit = system(cmd);
306 printf("\033[31mRemote commands are not succeeding.\033[0m\n\n");
311 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
312 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
313 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
314 cmdexit = system(cmd);
317 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
318 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
319 cmdexit = system(cmd);
323 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
324 "the name of the directory on %s which contains the 'sendcommand' program.\n"
325 "(example: /opt/foo/citadel)\n"
326 "--> ", remote_host);
328 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
329 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
330 cmdexit = system(cmd);
332 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
338 printf( "\033[2J\033[H\n"
339 " \033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
341 " ║ \033[33mMigrating from: %-50s\033[32m║\n"
343 " ╟═══════════════════════════════════════════════════════════════════╣\n"
345 " ║ Lines received: 0 Percent complete: 0 ║\n"
347 " ╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
351 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
352 sourcefp = popen(cmd, "r");
355 printf("\n%s\n\n", strerror(errno));
360 serv_puts(local_admin_socket, "MIGR import");
361 serv_gets(local_admin_socket, buf);
363 printf("\n\033[31m%s\033[0m\n", buf);
370 time_t last_update = time(NULL);
371 while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
372 ptr = strchr(buf, '\n');
373 if (ptr) *ptr = 0; // remove the newline character
375 if (!strncasecmp(buf, "<progress>", 10)) {
376 printf("\033[8;75H\033[33m%d\033[0m\033[20;0H\n", atoi(&buf[10]));
378 if (time(NULL) != last_update) {
379 last_update = time(NULL);
380 printf("\033[8;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
382 serv_puts(local_admin_socket, buf);
385 serv_puts(local_admin_socket, "000");
388 // FIXME restart the local server now
391 printf("Closing the data connection from the source system...\n");
395 ctdlmigrate_exit(cmdexit);