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 <libcitadel.h>
43 #include "citadel_dirs.h"
48 * Replacement for gets() that doesn't throw a compiler warning.
49 * We're only using it for some simple prompts, so we don't need
50 * to worry about attackers exploiting it.
52 void getz(char *buf) {
55 ptr = fgets(buf, SIZ, stdin);
61 ptr = strchr(buf, '\n');
69 int main(int argc, char *argv[]) {
70 char ctdldir[PATH_MAX]=CTDLDIR;
72 char sendcommand[PATH_MAX];
76 char socket_path[PATH_MAX];
77 char remote_user[SIZ];
78 char remote_host[SIZ];
79 char remote_sendcommand[PATH_MAX];
80 FILE *sourcefp = NULL;
81 FILE *targetfp = NULL;
83 char spinning[4] = "-\\|/" ;
86 CtdlMakeTempFileName(socket_path, sizeof socket_path);
87 if (chdir(ctdldir) != 0) {
88 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
93 printf( "\033[2J\033[H\n"
94 "-------------------------------------------\n"
95 "Over-the-wire migration utility for Citadel\n"
96 "-------------------------------------------\n"
98 "This utility is designed to migrate your Citadel installation\n"
99 "to a new host system via a network connection, without disturbing\n"
100 "the source system. The target may be a different CPU architecture\n"
101 "and/or operating system. The source system should be running\n"
102 "Citadel version %d or newer, and the target system should be running\n"
103 "either the same version or a newer version. You will also need\n"
104 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
106 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
107 "on this system will be ERASED.\n"
109 "Do you wish to continue? "
114 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
118 printf("\n\nGreat! First we will check some things out here on our target\n"
119 "system to make sure it is ready to receive data.\n\n");
121 printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
122 snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
123 snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
124 cmdexit = system(cmd);
126 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
127 "here on the target system. Is Citadel running?\n\n");
130 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
132 printf("Enter the host name or IP address of the source system\n"
133 "(example: ctdl.foo.org)\n"
138 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
139 "(usually root)\n--> ",
142 if (IsEmptyStr(remote_user))
143 goto get_remote_user;
145 printf("\nEstablishing an SSH connection to the source system...\n\n");
147 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
148 cmdexit = system(cmd);
151 printf("This program was unable to establish an SSH session to the source system.\n\n");
156 printf("\nTesting a command over the connection...\n\n");
157 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
158 socket_path, remote_user, remote_host);
159 cmdexit = system(cmd);
162 printf("Remote commands are not succeeding.\n\n");
167 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
168 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
169 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
170 socket_path, remote_user, remote_host, remote_sendcommand);
171 cmdexit = system(cmd);
173 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
174 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
175 socket_path, remote_user, remote_host, remote_sendcommand);
176 cmdexit = system(cmd);
179 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
180 "the name of the directory on %s which contains the 'sendcommand' program.\n"
181 "(example: /opt/foo/citadel)\n"
182 "--> ", remote_host);
184 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
185 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
186 socket_path, remote_user, remote_host, remote_sendcommand);
187 cmdexit = system(cmd);
191 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
196 printf("ctdlmigrate will now begin a database migration...\n");
197 printf(" if the system doesn't start working, \n");
198 printf(" have a look at the syslog for pending jobs needing to be terminated.\n");
200 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
201 socket_path, remote_user, remote_host, remote_sendcommand);
202 sourcefp = popen(cmd, "r");
204 printf("\n%s\n\n", strerror(errno));
209 snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
210 targetfp = popen(cmd, "w");
212 printf("\n%s\n\n", strerror(errno));
217 while (fgets(buf, sizeof buf, sourcefp) != NULL) {
218 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
220 printf("%s\n", strerror(errno));
224 if ((linecount % 100) == 0) {
225 printf("%c\r", spinning[((linecount / 100) % 4)]);
230 FAIL: if (sourcefp) pclose(sourcefp);
231 if (targetfp) pclose(targetfp);
232 if (exitcode != 0) goto THEEND;
234 /* We need to copy a bunch of other stuff, and will do so using rsync */
236 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
237 socket_path, remote_user, remote_host, remote_sendcommand);
238 sourcefp = popen(cmd, "r");
240 printf("\n%s\n\n", strerror(errno));
244 while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
245 buf[strlen(buf)-1] = 0;
247 if (!strncasecmp(buf, "files|", 6)) {
248 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
249 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
251 else if (!strncasecmp(buf, "messages|", 9)) {
252 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
253 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
255 else if (!strncasecmp(buf, "keys|", 5)) {
256 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
257 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
260 strcpy(cmd, "false"); /* cheap and sleazy way to throw an error */
263 cmdexit = system(cmd);
270 THEEND: if (exitcode == 0) {
271 printf("\n\n *** Citadel migration was successful! *** \n\n");
274 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
276 printf("\nShutting down the socket connection...\n\n");
277 snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
278 socket_path, remote_user, remote_host);
279 cmdexit = system(cmd);
282 printf("There was an error shutting down the socket.\n\n");