citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

ctdlmigrate.c
Go to the documentation of this file.
1// Across-the-wire migration utility for Citadel
2//
3// Copyright (c) 2009-2021 citadel.org
4//
5// This program is open source software; you can redistribute it and/or modify
6// it under the terms of the GNU General Public License version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12
13#include <stdlib.h>
14#include <unistd.h>
15#include <stdio.h>
16#include <string.h>
17#include <ctype.h>
18#include <fcntl.h>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <sys/utsname.h>
22#include <sys/wait.h>
23#include <signal.h>
24#include <netdb.h>
25#include <errno.h>
26#include <limits.h>
27#include <pwd.h>
28#include <time.h>
29#include <readline/readline.h>
30#include <sys/socket.h>
31#include <sys/un.h>
32#include <libcitadel.h>
33#include "citadel.h"
34#include "axdefs.h"
35#include "sysdep.h"
36#include "config.h"
37#include "citadel_dirs.h"
38
39
40// support for getz() -- (globals would not be appropriate in a multithreaded program)
41static int gmaxlen = 0;
42static char *gdeftext = NULL;
43
44
45// support function for getz()
46static int limit_rl(FILE *dummy) {
47 if (rl_end > gmaxlen) {
48 return '\b';
49 }
50 return rl_getc(dummy);
51}
52
53
54// support function for getz()
55static int getz_deftext(void) {
56 if (gdeftext) {
57 rl_insert_text(gdeftext);
58 gdeftext = NULL;
59 rl_startup_hook = NULL;
60 }
61 return 0;
62}
63
64
65// Replacement for gets() that uses libreadline.
66void getz(char *buf, int maxlen, char *default_value, char *prompt) {
67 rl_startup_hook = getz_deftext;
68 rl_getc_function = limit_rl;
69 gmaxlen = maxlen;
70 gdeftext = default_value;
71 strcpy(buf, readline(prompt));
72}
73
74
75// Exit from the program while displaying an error code
76void ctdlmigrate_exit(int cmdexit) {
77 printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
78 exit(cmdexit);
79}
80
81
82// Connect to a Citadel on a remote host using a TCP/IP socket
83static int tcp_connectsock(char *host, char *service) {
84 struct in6_addr serveraddr;
85 struct addrinfo hints;
86 struct addrinfo *res = NULL;
87 struct addrinfo *ai = NULL;
88 int rc = (-1);
89 int sock = (-1);
90
91 if ((host == NULL) || IsEmptyStr(host)) {
92 return(-1);
93 }
94 if ((service == NULL) || IsEmptyStr(service)) {
95 return(-1);
96 }
97
98 memset(&hints, 0x00, sizeof(hints));
99 hints.ai_flags = AI_NUMERICSERV;
100 hints.ai_family = AF_UNSPEC;
101 hints.ai_socktype = SOCK_STREAM;
102
103 // Handle numeric IPv4 and IPv6 addresses
104 rc = inet_pton(AF_INET, host, &serveraddr);
105 if (rc == 1) { // dotted quad
106 hints.ai_family = AF_INET;
107 hints.ai_flags |= AI_NUMERICHOST;
108 } else {
109 rc = inet_pton(AF_INET6, host, &serveraddr);
110 if (rc == 1) { // IPv6 address
111 hints.ai_family = AF_INET6;
112 hints.ai_flags |= AI_NUMERICHOST;
113 }
114 }
115
116 // Begin the connection process
117 rc = getaddrinfo(host, service, &hints, &res);
118 if (rc != 0) {
119 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
120 return (-1);
121 }
122
123 // Try all available addresses until we connect to one or until we run out.
124 for (ai = res; ai != NULL; ai = ai->ai_next) {
125 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
126 if (sock < 0) {
127 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
128 return (-1);
129 }
130
131 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
132 if (rc >= 0) {
133 return (sock); // Connected!
134 }
135 else {
136 fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
137 close(sock); // Failed. Close the socket to avoid fd leak!
138 }
139 }
140 return (-1);
141}
142
143
144// Connect to a Citadel on a remote host using a unix domaion socket
145int uds_connectsock(char *sockpath) {
146 int s;
147 struct sockaddr_un addr;
148
149 memset(&addr, 0, sizeof(addr));
150 addr.sun_family = AF_UNIX;
151 strcpy(addr.sun_path, sockpath);
152
153 s = socket(AF_UNIX, SOCK_STREAM, 0);
154 if (s < 0) {
155 fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
156 return(-1);
157 }
158
159 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
160 fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
161 close(s);
162 return(-1);
163 }
164
165 return s;
166}
167
168
169// input binary data from socket
170void serv_read(int serv_sock, char *buf, int bytes) {
171 int len = 0;
172 int rlen = 0;
173
174 while (len < bytes) {
175 rlen = read(serv_sock, &buf[len], bytes - len);
176 if (rlen < 1) {
177 return;
178 }
179 len = len + rlen;
180 }
181}
182
183
184// send binary to server
185void serv_write(int serv_sock, char *buf, int nbytes) {
186 int bytes_written = 0;
187 int retval;
188 while (bytes_written < nbytes) {
189 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
190 if (retval < 1) {
191 return;
192 }
193 bytes_written = bytes_written + retval;
194 }
195}
196
197
198// input string from socket - implemented in terms of serv_read()
199void serv_gets(int serv_sock, char *buf) {
200 int i;
201
202 // Read one character at a time.
203 for (i = 0;; i++) {
204 serv_read(serv_sock, &buf[i], 1);
205 if (buf[i] == '\n' || i == (SIZ-1))
206 break;
207 }
208
209 // If we got a long line, discard characters until the newline.
210 if (i == (SIZ-1)) {
211 while (buf[i] != '\n') {
212 serv_read(serv_sock, &buf[i], 1);
213 }
214 }
215
216 // Strip all trailing nonprintables (crlf)
217 buf[i] = 0;
218}
219
220
221// send line to server - implemented in terms of serv_write()
222void serv_puts(int serv_sock, char *buf) {
223 serv_write(serv_sock, buf, strlen(buf));
224 serv_write(serv_sock, "\n", 1);
225}
226
227
228// send formatted printable data to the server
229void serv_printf(int serv_sock, const char *format, ...) {
230 va_list arg_ptr;
231 char buf[1024];
232
233 va_start(arg_ptr, format);
234 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
235 buf[sizeof buf - 2] = '\n';
236 serv_write(serv_sock, buf, strlen(buf));
237 va_end(arg_ptr);
238}
239
240
241// You know what main() does. If you don't, you shouldn't be trying to understand this program.
242int main(int argc, char *argv[]) {
243 char ctdldir[PATH_MAX]=CTDLDIR;
244 char yesno[2];
245 int cmdexit = 0; // when something fails, set cmdexit to nonzero, and skip to the end
246 char buf[PATH_MAX];
247
248 char remote_user[128];
249 char remote_host[128];
250 char remote_pass[128];
251 char *remote_port = "504";
252
253 int linecount = 0;
254 int a;
255 int remote_server_socket = (-1);
256 int local_admin_socket = (-1);
257 int progress = 0;
258
259 // Parse command line
260 while ((a = getopt(argc, argv, "h:")) != EOF) {
261 switch (a) {
262 case 'h':
263 strcpy(ctdldir, optarg);
264 break;
265 default:
266 fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
267 return(1);
268 }
269 }
270
271 if (chdir(ctdldir) != 0) {
272 fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
273 exit(errno);
274 }
275
276 signal(SIGINT, ctdlmigrate_exit);
277 signal(SIGQUIT, ctdlmigrate_exit);
278 signal(SIGTERM, ctdlmigrate_exit);
279 signal(SIGSEGV, ctdlmigrate_exit);
280 signal(SIGHUP, ctdlmigrate_exit);
281 signal(SIGPIPE, ctdlmigrate_exit);
282
283 printf( "\033[2J\033[H\n"
284 " \033[32m╔═══════════════════════════════════════════════╗\n"
285 " ║ ║\n"
286 " ║ \033[33mCitadel over-the-wire migration utility \033[32m║\n"
287 " ║ ║\n"
288 " ╚═══════════════════════════════════════════════╝\033[0m\n"
289 "\n"
290 "This utility is designed to migrate your Citadel installation\n"
291 "to a new host system via a network connection, without disturbing\n"
292 "the source system. The target may be a different CPU architecture\n"
293 "and/or operating system. The source system should be running\n"
294 "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
295 "either the same version or a newer version.\n"
296 "\n"
297 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
298 "on this system will be ERASED. Your target system will be on this\n"
299 "host in %s.\n"
300 "\n",
301 EXPORT_REV_MIN, ctdldir
302 );
303
304 getz(yesno, 1, NULL, "Do you wish to continue? ");
305 puts(yesno);
306 if (tolower(yesno[0]) != 'y') {
307 cmdexit = 101;
308 }
309
310 if (!cmdexit) {
311 printf( "\033[2J\033[H\n"
312 "\033[32m╔═══════════════════════════════════════════════╗\n"
313 "║ ║\n"
314 "║ \033[33mValidate source and target systems\033[32m ║\n"
315 "║ ║\n"
316 "╚═══════════════════════════════════════════════╝\033[0m\n"
317 "\n\n");
318
319 printf("First we must validate that the local target system is running\n");
320 printf("and ready to receive data.\n");
321 printf("Checking connectivity to Citadel in %s...\n", ctdldir);
322 local_admin_socket = uds_connectsock("citadel-admin.socket");
323 if (local_admin_socket < 0) {
324 cmdexit = 102;
325 }
326 }
327
328 if (!cmdexit) {
329 serv_gets(local_admin_socket, buf);
330 puts(buf);
331 if (buf[0] != '2') {
332 cmdexit = 103;
333 }
334 }
335
336 if (!cmdexit) {
337 serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
338 serv_gets(local_admin_socket, buf);
339 puts(buf);
340 if (buf[0] != '2') {
341 cmdexit = 104;
342 }
343 }
344
345 if (!cmdexit) {
346 printf("\n");
347 printf("OK, this side is ready to go. Now we must connect to the source system.\n\n");
348
349 getz(remote_host, sizeof remote_host, NULL, "Enter the name or IP address of the source system: ");
350 getz(remote_user, sizeof remote_user, "admin", " Enter the user name of an administrator account: ");
351 getz(remote_pass, sizeof remote_pass, NULL, " Enter the password for this account: ");
352
353 remote_server_socket = tcp_connectsock(remote_host, remote_port);
354 if (remote_server_socket < 0) {
355 cmdexit = 105;
356 }
357 }
358
359 if (!cmdexit) {
360 serv_gets(remote_server_socket, buf);
361 puts(buf);
362 if (buf[0] != '2') {
363 cmdexit = 106;
364 }
365 }
366
367 if (!cmdexit) {
368 serv_printf(remote_server_socket, "USER %s\n", remote_user);
369 serv_gets(remote_server_socket, buf);
370 puts(buf);
371 if (buf[0] != '3') {
372 cmdexit = 107;
373 }
374 }
375
376 if (!cmdexit) {
377 serv_printf(remote_server_socket, "PASS %s\n", remote_pass);
378 serv_gets(remote_server_socket, buf);
379 puts(buf);
380 if (buf[0] != '2') {
381 cmdexit = 108;
382 }
383 }
384
385 if (!cmdexit) {
386 printf( "\033[2J\033[H\n"
387 "\033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
388 "║ ║\n"
389 "║ \033[33mMigrating from: %-50s\033[32m║\n"
390 "║ ║\n"
391 "╠═══════════════════════════════════════════════════════════════════╣\n"
392 "║ ║\n"
393 "║ Lines received: 0 Percent complete: 0 ║\n"
394 "║ ║\n"
395 "╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
396 "\n", remote_host
397 );
398 }
399
400 if (!cmdexit) {
401 serv_puts(remote_server_socket, "MIGR export");
402 serv_gets(remote_server_socket, buf);
403 if (buf[0] != '1') {
404 printf("\n\033[31m%s\033[0m\n", buf);
405 cmdexit = 109;
406 }
407 }
408
409 if (!cmdexit) {
410 serv_puts(local_admin_socket, "MIGR import");
411 serv_gets(local_admin_socket, buf);
412 if (buf[0] != '4') {
413 printf("\n\033[31m%s\033[0m\n", buf);
414 cmdexit = 110;
415 }
416 }
417
418 if (!cmdexit) {
419 char *ptr;
420 time_t last_update = time(NULL);
421
422 while (serv_gets(remote_server_socket, buf), strcmp(buf, "000")) {
423 ptr = strchr(buf, '\n');
424 if (ptr) *ptr = 0; // remove the newline character
425 ++linecount;
426 if (!strncasecmp(buf, "<progress>", 10)) {
427 progress = atoi(&buf[10]);
428 printf("\033[8;65H\033[33m%d\033[0m\033[12;0H\n", progress);
429 }
430 if (time(NULL) != last_update) {
431 last_update = time(NULL);
432 printf("\033[8;19H\033[33m%d\033[0m\033[12;0H\n", linecount);
433 }
434 serv_puts(local_admin_socket, buf);
435 }
436
437 serv_puts(local_admin_socket, "000");
438 }
439
440 if ( (cmdexit == 0) && (progress < 100) ) {
441 printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
442 cmdexit = 111;
443 }
444
445 if (!cmdexit) {
446 printf("\033[36mMigration is complete. Restarting the target server.\033[0m\n");
447 serv_puts(local_admin_socket, "DOWN 1");
448 serv_gets(local_admin_socket, buf);
449 puts(buf);
450 printf("\033[36mIt is recommended that you shut down the source server now.\033[0m\n");
451 printf("\033[36mRemember to copy over your SSL keys and file libraries if appropriate.\033[0m\n");
452 }
453
454 close(remote_server_socket);
455 close(local_admin_socket);
456 ctdlmigrate_exit(cmdexit);
457}
#define EXPORT_REV_MIN
Definition: citadel.h:37
int serv_sock
Definition: citmail.c:31
int main(int argc, char *argv[])
Definition: ctdlmigrate.c:242
void ctdlmigrate_exit(int cmdexit)
Definition: ctdlmigrate.c:76
void serv_printf(int serv_sock, const char *format,...)
Definition: ctdlmigrate.c:229
static int getz_deftext(void)
Definition: ctdlmigrate.c:55
int uds_connectsock(char *sockpath)
Definition: ctdlmigrate.c:145
static int gmaxlen
Definition: ctdlmigrate.c:41
void serv_gets(int serv_sock, char *buf)
Definition: ctdlmigrate.c:199
void serv_puts(int serv_sock, char *buf)
Definition: ctdlmigrate.c:222
void serv_read(int serv_sock, char *buf, int bytes)
Definition: ctdlmigrate.c:170
static char * gdeftext
Definition: ctdlmigrate.c:42
void getz(char *buf, int maxlen, char *default_value, char *prompt)
Definition: ctdlmigrate.c:66
static int limit_rl(FILE *dummy)
Definition: ctdlmigrate.c:46
static int tcp_connectsock(char *host, char *service)
Definition: ctdlmigrate.c:83
void serv_write(int serv_sock, char *buf, int nbytes)
Definition: ctdlmigrate.c:185
void progress(char *text, long int curr, long int cmax)
Definition: setup.c:291
int yesno(const char *question, int default_value)
Definition: setup.c:237
int vsnprintf(char *buf, size_t max, const char *fmt, va_list argp)
Definition: snprintf.c:48
#define SIZ
Definition: sysconfig.h:33