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[])
73 char relhome[PATH_MAX]="";
74 char ctdldir[PATH_MAX]=CTDLDIR;
76 char sendcommand[PATH_MAX];
80 char socket_path[PATH_MAX];
81 char remote_user[SIZ];
82 char remote_host[SIZ];
83 char remote_sendcommand[PATH_MAX];
84 FILE *sourcefp = NULL;
85 FILE *targetfp = NULL;
87 char spinning[4] = "-\\|/" ;
90 CtdlMakeTempFileName(socket_path, sizeof socket_path);
92 cmdexit = system("clear");
93 printf( "-------------------------------------------\n"
94 "Over-the-wire migration utility for Citadel\n"
95 "-------------------------------------------\n"
97 "This utility is designed to migrate your Citadel installation\n"
98 "to a new host system via a network connection, without disturbing\n"
99 "the source system. The target may be a different CPU architecture\n"
100 "and/or operating system. The source system should be running\n"
101 "Citadel version %d or newer, and the target system should be running\n"
102 "either the same version or a newer version. You will also need\n"
103 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
105 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
106 "on this system will be ERASED.\n"
108 "Do you wish to continue? "
113 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
117 printf("\n\nGreat! First we will check some things out here on our target\n"
118 "system to make sure it is ready to receive data.\n\n");
120 printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
121 snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
122 snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
123 cmdexit = system(cmd);
125 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
126 "here on the target system. Is Citadel running?\n\n");
129 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
131 printf("Enter the host name or IP address of the source system\n"
132 "(example: ctdl.foo.org)\n"
137 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
138 "(usually root)\n--> ",
141 if (IsEmptyStr(remote_user))
142 goto get_remote_user;
144 printf("\nEstablishing an SSH connection to the source system...\n\n");
146 snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
147 cmdexit = system(cmd);
150 printf("This program was unable to establish an SSH session to the source system.\n\n");
155 printf("\nTesting a command over the connection...\n\n");
156 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
157 socket_path, remote_user, remote_host);
158 cmdexit = system(cmd);
161 printf("Remote commands are not succeeding.\n\n");
166 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
167 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
168 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
169 socket_path, remote_user, remote_host, remote_sendcommand);
170 cmdexit = system(cmd);
172 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
173 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
174 socket_path, remote_user, remote_host, remote_sendcommand);
175 cmdexit = system(cmd);
178 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
179 "the name of the directory on %s which contains the 'sendcommand' program.\n"
180 "(example: /opt/foo/citadel)\n"
181 "--> ", remote_host);
183 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
184 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
185 socket_path, remote_user, remote_host, remote_sendcommand);
186 cmdexit = system(cmd);
190 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
195 printf("ctdlmigrate will now begin a database migration...\n");
196 printf(" if the system doesn't start working, \n");
197 printf(" have a look at the syslog for pending jobs needing to be terminated.\n");
199 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
200 socket_path, remote_user, remote_host, remote_sendcommand);
201 sourcefp = popen(cmd, "r");
203 printf("\n%s\n\n", strerror(errno));
208 snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
209 targetfp = popen(cmd, "w");
211 printf("\n%s\n\n", strerror(errno));
216 while (fgets(buf, sizeof buf, sourcefp) != NULL) {
217 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
219 printf("%s\n", strerror(errno));
223 if ((linecount % 100) == 0) {
224 printf("%c\r", spinning[((linecount / 100) % 4)]);
229 FAIL: if (sourcefp) pclose(sourcefp);
230 if (targetfp) pclose(targetfp);
231 if (exitcode != 0) goto THEEND;
233 /* We need to copy a bunch of other stuff, and will do so using rsync */
235 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
236 socket_path, remote_user, remote_host, remote_sendcommand);
237 sourcefp = popen(cmd, "r");
239 printf("\n%s\n\n", strerror(errno));
243 while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
244 buf[strlen(buf)-1] = 0;
246 if (!strncasecmp(buf, "files|", 6)) {
247 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
248 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
250 else if (!strncasecmp(buf, "messages|", 9)) {
251 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
252 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
254 else if (!strncasecmp(buf, "keys|", 5)) {
255 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
256 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
259 strcpy(cmd, "false"); /* cheap and sleazy way to throw an error */
262 cmdexit = system(cmd);
269 THEEND: if (exitcode == 0) {
270 printf("\n\n *** Citadel migration was successful! *** \n\n");
273 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
275 printf("\nShutting down the socket connection...\n\n");
276 snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
277 socket_path, remote_user, remote_host);
278 cmdexit = system(cmd);
281 printf("There was an error shutting down the socket.\n\n");