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"
43 * Replacement for gets() that doesn't throw a compiler warning.
44 * We're only using it for some simple prompts, so we don't need
45 * to worry about attackers exploiting it.
47 void getz(char *buf) {
50 ptr = fgets(buf, SIZ, stdin);
56 ptr = strchr(buf, '\n');
61 int uds_connectsock(char *sockpath) {
63 struct sockaddr_un addr;
65 memset(&addr, 0, sizeof(addr));
66 addr.sun_family = AF_UNIX;
67 strcpy(addr.sun_path, sockpath);
69 s = socket(AF_UNIX, SOCK_STREAM, 0);
71 fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
75 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
76 fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
86 * input binary data from socket
88 void serv_read(int serv_sock, char *buf, int bytes) {
93 rlen = read(serv_sock, &buf[len], bytes - len);
103 * send binary to server
105 void serv_write(int serv_sock, char *buf, int nbytes) {
106 int bytes_written = 0;
108 while (bytes_written < nbytes) {
109 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
113 bytes_written = bytes_written + retval;
119 * input string from socket - implemented in terms of serv_read()
121 void serv_gets(int serv_sock, char *buf) {
124 /* Read one character at a time.
127 serv_read(serv_sock, &buf[i], 1);
128 if (buf[i] == '\n' || i == (SIZ-1))
132 /* If we got a long line, discard characters until the newline.
135 while (buf[i] != '\n') {
136 serv_read(serv_sock, &buf[i], 1);
140 /* Strip all trailing nonprintables (crlf)
147 * send line to server - implemented in terms of serv_write()
149 void serv_puts(int serv_sock, char *buf) {
150 serv_write(serv_sock, buf, strlen(buf));
151 serv_write(serv_sock, "\n", 1);
155 int main(int argc, char *argv[]) {
156 char ctdldir[PATH_MAX]=CTDLDIR;
158 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
161 char socket_path[PATH_MAX];
162 char remote_user[SIZ];
163 char remote_host[SIZ];
164 char remote_sendcommand[PATH_MAX];
165 FILE *sourcefp = NULL;
168 int local_admin_socket = (-1);
171 /* Parse command line */
172 while ((a = getopt(argc, argv, "h:")) != EOF) {
175 strcpy(ctdldir, optarg);
178 fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
183 if (chdir(ctdldir) != 0) {
184 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
188 printf( "\033[2J\033[H\n"
189 " \033[32m╔═══════════════════════════════════════════════╗\n"
191 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
193 " ╚═══════════════════════════════════════════════╝\033[0m\n"
195 "This utility is designed to migrate your Citadel installation\n"
196 "to a new host system via a network connection, without disturbing\n"
197 "the source system. The target may be a different CPU architecture\n"
198 "and/or operating system. The source system should be running\n"
199 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
200 "either the same version or a newer version. You will also need\n"
201 "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\n"
203 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
204 "on this system will be ERASED. Your target system will be on this\n"
207 "\033[33mDo you wish to continue? \033[0m"
209 EXPORT_REV_MIN, ctdldir
212 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
218 printf( "\033[2J\033[H\n"
219 " \033[32m╔═══════════════════════════════════════════════╗\n"
221 " ║ \033[33mValidate source and target systems\033[32m ║\n"
223 " ╚═══════════════════════════════════════════════╝\033[0m\n"
226 printf("First we must validate that the local target system is running and ready to receive data.\n");
227 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
228 local_admin_socket = uds_connectsock("citadel-admin.socket");
230 serv_gets(local_admin_socket, buf);
238 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
239 serv_gets(local_admin_socket, buf);
247 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
248 printf("Enter the host name or IP address of the source system\n"
249 "(example: ctdl.foo.org)\n"
253 while (IsEmptyStr(remote_user)) {
254 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
255 "(usually root)\n--> ",
260 printf("\nEstablishing an SSH connection to the source system...\n\n");
261 sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
264 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
267 printf("%s\n", strerror(errno));
270 else if (sshpid == 0) {
271 execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
274 else { // Wait for SSH to go into the background
275 waitpid(sshpid, &cmdexit, 0);
280 printf("\nTesting a command over the connection...\n\n");
281 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
282 socket_path, remote_user, remote_host);
283 cmdexit = system(cmd);
286 printf("\033[31mRemote commands are not succeeding.\033[0m\n\n");
291 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
292 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
293 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
294 cmdexit = system(cmd);
297 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
298 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
299 cmdexit = system(cmd);
303 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
304 "the name of the directory on %s which contains the 'sendcommand' program.\n"
305 "(example: /opt/foo/citadel)\n"
306 "--> ", remote_host);
308 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
309 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
310 cmdexit = system(cmd);
312 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
318 printf( "\033[2J\033[H\n"
319 " \033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
321 " ║ \033[33mMigrating from: %-50s\033[32m║\n"
323 " ╟═══════════════════════════════════════════════════════════════════╣\n"
325 " ║ Lines received: 0 Percent complete: 0 ║\n"
327 " ╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
331 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
332 sourcefp = popen(cmd, "r");
335 printf("\n%s\n\n", strerror(errno));
340 serv_puts(local_admin_socket, "MIGR import");
341 serv_gets(local_admin_socket, buf);
343 printf("\n\033[31m%s\033[0m\n", buf);
350 time_t last_update = time(NULL);
351 while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
352 ptr = strchr(buf, '\n');
353 if (ptr) *ptr = 0; // remove the newline character
355 if (!strncasecmp(buf, "<progress>", 10)) {
356 printf("\033[8;75H\033[33m%d\033[0m\033[20;0H\n", atoi(&buf[10]));
358 if (time(NULL) != last_update) {
359 last_update = time(NULL);
360 printf("\033[8;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
362 serv_puts(local_admin_socket, buf);
365 serv_puts(local_admin_socket, "000");
368 // FIXME restart the local server now
371 printf("Closing the data connection from the source system...\n");
374 printf("Shutting down the socket connection...\n");
376 printf("Shutting down the SSH session...\n");
377 kill(sshpid, SIGKILL);
378 printf("\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);