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 "-------------------------------------------\n"
190 "Over-the-wire migration utility for Citadel\n"
191 "-------------------------------------------\n"
193 "This utility is designed to migrate your Citadel installation\n"
194 "to a new host system via a network connection, without disturbing\n"
195 "the source system. The target may be a different CPU architecture\n"
196 "and/or operating system. The source system should be running\n"
197 "Citadel version %d or newer, and the target system should be running\n"
198 "either the same version or a newer version. You will also need\n"
199 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
201 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
202 "on this system will be ERASED. Your target system will be on this\n"
205 "Do you wish to continue? "
207 EXPORT_REV_MIN, ctdldir
210 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
215 printf("\n\nGreat! First we will check some things out here on our target\n"
216 "system to make sure it is ready to receive data.\n\n");
218 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
219 local_admin_socket = uds_connectsock("citadel-admin.socket");
221 serv_gets(local_admin_socket, buf);
229 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
230 serv_gets(local_admin_socket, buf);
238 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
239 printf("Enter the host name or IP address of the source system\n"
240 "(example: ctdl.foo.org)\n"
244 while (IsEmptyStr(remote_user)) {
245 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
246 "(usually root)\n--> ",
251 printf("\nEstablishing an SSH connection to the source system...\n\n");
252 sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
255 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
258 printf("%s\n", strerror(errno));
261 else if (sshpid == 0) {
262 execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
265 else { // Wait for SSH to go into the background
266 waitpid(sshpid, NULL, 0);
271 printf("\nTesting a command over the connection...\n\n");
272 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
273 socket_path, remote_user, remote_host);
274 cmdexit = system(cmd);
277 printf("Remote commands are not succeeding.\n\n");
282 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
283 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
284 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
285 cmdexit = system(cmd);
288 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
289 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
290 cmdexit = system(cmd);
294 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
295 "the name of the directory on %s which contains the 'sendcommand' program.\n"
296 "(example: /opt/foo/citadel)\n"
297 "--> ", remote_host);
299 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
300 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
301 cmdexit = system(cmd);
303 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
310 printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
312 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
313 sourcefp = popen(cmd, "r");
316 printf("\n%s\n\n", strerror(errno));
321 serv_puts(local_admin_socket, "MIGR import");
322 serv_gets(local_admin_socket, buf);
324 printf("\n%s\n", buf);
331 time_t last_update = time(NULL);
332 while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
333 ptr = strchr(buf, '\n');
334 if (ptr) *ptr = 0; // remove the newline character
336 if (!strncasecmp(buf, "<progress>", 10)) {
337 printf("\033[11;0HPercent complete: \033[32m%d\033[0m\n", atoi(&buf[10]));
339 if (time(NULL) != last_update) {
340 last_update = time(NULL);
341 printf("\033[10;0H Lines received: \033[32m%d\033[0m\n", linecount);
343 serv_puts(local_admin_socket, buf);
346 serv_puts(local_admin_socket, "000");
349 // FIXME restart the local server now
352 printf("\nShutting down the socket connection...\n\n");
354 kill(sshpid, SIGKILL);