2 * Across-the-wire migration utility for Citadel
4 * Yes, we used goto, and gets(), and committed all sorts of other heinous sins here.
5 * The scope of this program isn't wide enough to make a difference. If you don't like
6 * it you can rewrite it.
8 * Copyright (c) 2009-2021 citadel.org
10 * This program is open source software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * (Note: a useful future enhancement might be to support "-h" on both sides)
28 #include <sys/types.h>
30 #include <sys/utsname.h>
38 #include <sys/socket.h>
40 #include <libcitadel.h>
45 #include "citadel_dirs.h"
50 * Replacement for gets() that doesn't throw a compiler warning.
51 * We're only using it for some simple prompts, so we don't need
52 * to worry about attackers exploiting it.
54 void getz(char *buf) {
57 ptr = fgets(buf, SIZ, stdin);
63 ptr = strchr(buf, '\n');
68 int uds_connectsock(char *sockpath) {
70 struct sockaddr_un addr;
72 memset(&addr, 0, sizeof(addr));
73 addr.sun_family = AF_UNIX;
74 strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
76 s = socket(AF_UNIX, SOCK_STREAM, 0);
78 fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
82 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
83 fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
93 * input binary data from socket
95 void serv_read(int serv_sock, char *buf, int bytes) {
100 rlen = read(serv_sock, &buf[len], bytes - len);
110 * send binary to server
112 void serv_write(int serv_sock, char *buf, int nbytes) {
113 int bytes_written = 0;
115 while (bytes_written < nbytes) {
116 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
120 bytes_written = bytes_written + retval;
126 * input string from socket - implemented in terms of serv_read()
128 void serv_gets(int serv_sock, char *buf) {
131 /* Read one character at a time.
134 serv_read(serv_sock, &buf[i], 1);
135 if (buf[i] == '\n' || i == (SIZ-1))
139 /* If we got a long line, discard characters until the newline.
142 while (buf[i] != '\n') {
143 serv_read(serv_sock, &buf[i], 1);
147 /* Strip all trailing nonprintables (crlf)
154 * send line to server - implemented in terms of serv_write()
156 void serv_puts(int serv_sock, char *buf) {
157 serv_write(serv_sock, buf, strlen(buf));
158 serv_write(serv_sock, "\n", 1);
162 int main(int argc, char *argv[]) {
163 char ctdldir[PATH_MAX]=CTDLDIR;
168 char socket_path[PATH_MAX];
169 char remote_user[SIZ];
170 char remote_host[SIZ];
171 char remote_sendcommand[PATH_MAX];
172 FILE *sourcefp = NULL;
175 int local_admin_socket = (-1);
177 /* Parse command line */
178 while ((a = getopt(argc, argv, "h:")) != EOF) {
181 strncpy(ctdldir, optarg, sizeof ctdldir);
184 fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
189 if (chdir(ctdldir) != 0) {
190 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
194 printf( "\033[2J\033[H\n"
195 "-------------------------------------------\n"
196 "Over-the-wire migration utility for Citadel\n"
197 "-------------------------------------------\n"
199 "This utility is designed to migrate your Citadel installation\n"
200 "to a new host system via a network connection, without disturbing\n"
201 "the source system. The target may be a different CPU architecture\n"
202 "and/or operating system. The source system should be running\n"
203 "Citadel version %d or newer, and the target system should be running\n"
204 "either the same version or a newer version. You will also need\n"
205 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
207 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
208 "on this system will be ERASED. Your target system will be on this\n"
211 "Do you wish to continue? "
213 EXPORT_REV_MIN, ctdldir
216 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
220 printf("\n\nGreat! First we will check some things out here on our target\n"
221 "system to make sure it is ready to receive data.\n\n");
223 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
224 local_admin_socket = uds_connectsock("citadel-admin.socket");
227 serv_gets(local_admin_socket, buf);
232 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
233 serv_gets(local_admin_socket, buf);
239 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
241 printf("Enter the host name or IP address of the source system\n"
242 "(example: ctdl.foo.org)\n"
246 while (IsEmptyStr(remote_user)) {
247 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
248 "(usually root)\n--> ",
253 printf("\nEstablishing an SSH connection to the source system...\n\n");
254 sprintf(socket_path, "/tmp/ctdlmigrate.XXXXXX");
257 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
258 cmdexit = system(cmd);
261 printf("This program was unable to establish an SSH session to the source system.\n\n");
265 printf("\nTesting a command over the connection...\n\n");
266 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
267 socket_path, remote_user, remote_host);
268 cmdexit = system(cmd);
271 printf("Remote commands are not succeeding.\n\n");
275 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
276 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
277 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
278 socket_path, remote_user, remote_host, remote_sendcommand);
279 cmdexit = system(cmd);
281 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
282 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
283 socket_path, remote_user, remote_host, remote_sendcommand);
284 cmdexit = system(cmd);
287 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
288 "the name of the directory on %s which contains the 'sendcommand' program.\n"
289 "(example: /opt/foo/citadel)\n"
290 "--> ", remote_host);
292 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
293 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
294 socket_path, remote_user, remote_host, remote_sendcommand);
295 cmdexit = system(cmd);
299 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
304 printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
306 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
307 sourcefp = popen(cmd, "r");
309 printf("\n%s\n\n", strerror(errno));
313 serv_puts(local_admin_socket, "MIGR import");
314 serv_gets(local_admin_socket, buf);
316 printf("\n%s\n", buf);
321 time_t time_started = time(NULL);
322 time_t last_update = time(NULL);
323 while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
324 ptr = strchr(buf, '\n');
327 if (!strncasecmp(buf, "<progress>", 10)) {
328 printf("\033[11;0HPercent complete: \033[32m%d\033[0m\n", atoi(&buf[10]));
330 if (time(NULL) != last_update) {
331 last_update = time(NULL);
332 printf("\033[10;0H Lines received: \033[32m%d\033[0m\n", linecount);
334 serv_puts(local_admin_socket, buf);
337 serv_puts(local_admin_socket, "000");
339 // FIXME restart the local server now
342 printf("\nShutting down the socket connection...\n\n");
343 snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s", socket_path, remote_user, remote_host);