formatting
[citadel.git] / citadel / ctdlmigrate.c
index 78af852bd6bdf80349daae4a4b30af0910a80c23..d18707a27319dece1fac47bf3314214783739100 100644 (file)
@@ -1,16 +1,14 @@
-/*
- * Across-the-wire migration utility for Citadel
- *
- * Copyright (c) 2009-2021 citadel.org
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
+// Across-the-wire migration utility for Citadel
+//
+// Copyright (c) 2009-2021 citadel.org
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
 
 #include <stdlib.h>
 #include <unistd.h>
@@ -28,6 +26,7 @@
 #include <limits.h>
 #include <pwd.h>
 #include <time.h>
+#include <readline/readline.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <libcitadel.h>
 #include "citadel_dirs.h"
 
 
+// yeah, this wouldn't work in a multithreaded program.
+static int gmaxlen = 0;
+static char *gdeftext = NULL;
 
-/*
- * Replacement for gets() that doesn't throw a compiler warning.
- * We're only using it for some simple prompts, so we don't need
- * to worry about attackers exploiting it.
- */
-void getz(char *buf) {
-       char *ptr;
 
-       ptr = fgets(buf, SIZ, stdin);
-       if (!ptr) {
-               buf[0] = 0;
-               return;
+// support function for getz()
+static int limit_rl(FILE *dummy) {
+       if (rl_end > gmaxlen) {
+               return '\b';
        }
+       return rl_getc(dummy);
+}
+
+
+// support function for getz()
+static int getz_deftext(void) {
+       if (gdeftext) {
+               rl_insert_text(gdeftext);
+               gdeftext = NULL;
+               rl_startup_hook = NULL;
+       }
+       return 0;
+}
+
+
+// Replacement for gets() that uses libreadline.
+void getz(char *buf, int maxlen, char *default_value, char *prompt) {
+       rl_startup_hook = getz_deftext;
+       rl_getc_function = limit_rl;
+       gmaxlen = maxlen;
+       gdeftext = default_value;
+       strcpy(buf, readline(prompt));
+}
+
+
+// Exit from the program while displaying an error code
+void ctdlmigrate_exit(int cmdexit) {
+       printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
+       exit(cmdexit);
+}
+
+
+// Connect to a Citadel on a remote host using a TCP/IP socket
+static int tcp_connectsock(char *host, char *service) {
+       struct in6_addr serveraddr;
+       struct addrinfo hints;
+       struct addrinfo *res = NULL;
+       struct addrinfo *ai = NULL;
+       int rc = (-1);
+       int sock = (-1);
+
+       if ((host == NULL) || IsEmptyStr(host)) {
+               return(-1);
+       }
+       if ((service == NULL) || IsEmptyStr(service)) {
+               return(-1);
+       }
+
+       memset(&hints, 0x00, sizeof(hints));
+       hints.ai_flags = AI_NUMERICSERV;
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       // Handle numeric IPv4 and IPv6 addresses
+       rc = inet_pton(AF_INET, host, &serveraddr);
+       if (rc == 1) {                                  // dotted quad
+               hints.ai_family = AF_INET;
+               hints.ai_flags |= AI_NUMERICHOST;
+       } else {
+               rc = inet_pton(AF_INET6, host, &serveraddr);
+               if (rc == 1) {                          // IPv6 address
+                       hints.ai_family = AF_INET6;
+                       hints.ai_flags |= AI_NUMERICHOST;
+               }
+       }
+
+       // Begin the connection process
+       rc = getaddrinfo(host, service, &hints, &res);
+       if (rc != 0) {
+               fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
+               return (-1);
+       }
+
+       // Try all available addresses until we connect to one or until we run out.
+       for (ai = res; ai != NULL; ai = ai->ai_next) {
+               sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+               if (sock < 0) {
+                       fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
+                       return (-1);
+               }
 
-       ptr = strchr(buf, '\n');
-       if (ptr) *ptr = 0;
+               rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
+               if (rc >= 0) {
+                       return (sock);  // Connected!
+               }
+               else {
+                       fprintf(stderr, "ctdlmigrate: %s\n", strerror(errno));
+                       close(sock);    // Failed.  Close the socket to avoid fd leak!
+               }
+       }
+       return (-1);
 }
 
 
+// Connect to a Citadel on a remote host using a unix domaion socket
 int uds_connectsock(char *sockpath) {
        int s;
        struct sockaddr_un addr;
@@ -68,27 +152,25 @@ int uds_connectsock(char *sockpath) {
 
        s = socket(AF_UNIX, SOCK_STREAM, 0);
        if (s < 0) {
-               fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
-               exit(3);
+               fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
+               return(-1);
        }
 
        if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-               fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
+               fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
                close(s);
-               exit(3);
+               return(-1);
        }
 
        return s;
 }
 
 
-/*
- * input binary data from socket
- */
+// input binary data from socket
 void serv_read(int serv_sock, char *buf, int bytes) {
-       int len, rlen;
+       int len = 0;
+       int rlen = 0;
 
-       len = 0;
        while (len < bytes) {
                rlen = read(serv_sock, &buf[len], bytes - len);
                if (rlen < 1) {
@@ -99,9 +181,7 @@ void serv_read(int serv_sock, char *buf, int bytes) {
 }
 
 
-/*
- * send binary to server
- */
+// send binary to server
 void serv_write(int serv_sock, char *buf, int nbytes) {
        int bytes_written = 0;
        int retval;
@@ -115,109 +195,137 @@ void serv_write(int serv_sock, char *buf, int nbytes) {
 }
 
 
-/*
- * input string from socket - implemented in terms of serv_read()
- */
+// input string from socket - implemented in terms of serv_read()
 void serv_gets(int serv_sock, char *buf) {
        int i;
 
-       /* Read one character at a time.
-        */
+       // Read one character at a time.
        for (i = 0;; i++) {
                serv_read(serv_sock, &buf[i], 1);
                if (buf[i] == '\n' || i == (SIZ-1))
                        break;
        }
 
-       /* If we got a long line, discard characters until the newline.
-        */
+       // If we got a long line, discard characters until the newline.
        if (i == (SIZ-1)) {
                while (buf[i] != '\n') {
                        serv_read(serv_sock, &buf[i], 1);
                }
        }
 
-       /* Strip all trailing nonprintables (crlf)
-        */
+       // Strip all trailing nonprintables (crlf)
        buf[i] = 0;
 }
 
 
-/*
- * send line to server - implemented in terms of serv_write()
- */
+// send line to server - implemented in terms of serv_write()
 void serv_puts(int serv_sock, char *buf) {
        serv_write(serv_sock, buf, strlen(buf));
        serv_write(serv_sock, "\n", 1);
 }
 
 
+// send formatted printable data to the server
+void serv_printf(int serv_sock, const char *format, ...) {   
+       va_list arg_ptr;   
+       char buf[1024];
+   
+       va_start(arg_ptr, format);   
+       if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
+               buf[sizeof buf - 2] = '\n';
+       serv_write(serv_sock, buf, strlen(buf)); 
+       va_end(arg_ptr);
+}
+
+
+// You know what main() does.  If you don't, you shouldn't be trying to understand this program.
 int main(int argc, char *argv[]) {
        char ctdldir[PATH_MAX]=CTDLDIR;
-       char yesno[5];
+       char yesno[2];
        int cmdexit = 0;                                // when something fails, set cmdexit to nonzero, and skip to the end
-       char cmd[PATH_MAX];
        char buf[PATH_MAX];
-       char socket_path[PATH_MAX];
-       char remote_user[SIZ];
-       char remote_host[SIZ];
-       char remote_sendcommand[PATH_MAX];
-       FILE *sourcefp = NULL;
+
+       char remote_user[128];
+       char remote_host[128];
+       char remote_pass[128];
+       char *remote_port = "504";
+
        int linecount = 0;
        int a;
+       int remote_server_socket = (-1);
        int local_admin_socket = (-1);
-       pid_t sshpid = (-1);
+       int progress = 0;
 
-       /* Parse command line */
+       // Parse command line
        while ((a = getopt(argc, argv, "h:")) != EOF) {
                switch (a) {
                case 'h':
                        strcpy(ctdldir, optarg);
                        break;
                default:
-                       fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
+                       fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
                        return(1);
                }
        }
 
        if (chdir(ctdldir) != 0) {
-               fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
+               fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
                exit(errno);
        }
 
+       signal(SIGINT, ctdlmigrate_exit);
+       signal(SIGQUIT, ctdlmigrate_exit);
+       signal(SIGTERM, ctdlmigrate_exit);
+       signal(SIGSEGV, ctdlmigrate_exit);
+       signal(SIGHUP, ctdlmigrate_exit);
+       signal(SIGPIPE, ctdlmigrate_exit);
+
        printf( "\033[2J\033[H\n"
-               "-------------------------------------------\n"
-               "Over-the-wire migration utility for Citadel\n"
-               "-------------------------------------------\n"
+               "          \033[32m╔═══════════════════════════════════════════════╗\n"
+               "          ║                                               ║\n"
+               "          ║    \033[33mCitadel over-the-wire migration utility    \033[32m║\n"
+               "          ║                                               ║\n"
+               "          ╚═══════════════════════════════════════════════╝\033[0m\n"
                "\n"
                "This utility is designed to migrate your Citadel installation\n"
                "to a new host system via a network connection, without disturbing\n"
                "the source system.  The target may be a different CPU architecture\n"
                "and/or operating system.  The source system should be running\n"
-               "Citadel version %d or newer, and the target system should be running\n"
-               "either the same version or a newer version.  You will also need\n"
-               "the 'rsync' utility, and OpenSSH v4 or newer.\n"
+               "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
+               "either the same version or a newer version.\n"
                "\n"
                "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
                "on this system will be ERASED.  Your target system will be on this\n"
                "host in %s.\n"
-               "\n"
-               "Do you wish to continue? "
-               ,
+               "\n",
                EXPORT_REV_MIN, ctdldir
        );
 
-       if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
+       getz(yesno, 1, NULL, "Do you wish to continue? ");
+       puts(yesno);
+       if (tolower(yesno[0]) != 'y') {
                cmdexit = 1;
        }
 
        if (!cmdexit) {
-               printf("\n\nGreat!  First we will check some things out here on our target\n"
-                       "system to make sure it is ready to receive data.\n\n");
+               printf( "\033[2J\033[H\n"
+                       "\033[32m╔═══════════════════════════════════════════════╗\n"
+                       "║                                               ║\n"
+                       "║       \033[33mValidate source and target systems\033[32m      ║\n"
+                       "║                                               ║\n"
+                       "╚═══════════════════════════════════════════════╝\033[0m\n"
+                       "\n\n");
        
+               printf("First we must validate that the local target system is running\n");
+               printf("and ready to receive data.\n");
                printf("Checking connectivity to Citadel in %s...\n", ctdldir);
                local_admin_socket = uds_connectsock("citadel-admin.socket");
+               if (local_admin_socket < 0) {
+                       cmdexit = 1;
+               }
+       }
 
+       if (!cmdexit) {
                serv_gets(local_admin_socket, buf);
                puts(buf);
                if (buf[0] != '2') {
@@ -235,85 +343,66 @@ int main(int argc, char *argv[]) {
        }
 
        if (!cmdexit) {
-               printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
-               printf("Enter the host name or IP address of the source system\n"
-                       "(example: ctdl.foo.org)\n"
-                       "--> ");
-               getz(remote_host);
-       
-               while (IsEmptyStr(remote_user)) {
-                       printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
-                               "(usually root)\n--> ",
-                               remote_host);
-                       getz(remote_user);
-               }
+               printf("\n");
+               printf("OK, this side is ready to go.  Now we must connect to the source system.\n\n");
 
-               printf("\nEstablishing an SSH connection to the source system...\n\n");
-               sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
-               unlink(socket_path);
+               getz(remote_host, sizeof remote_host, NULL,     "Enter the name or IP address of the source system: ");
+               getz(remote_user, sizeof remote_user, "admin",  "  Enter the user name of an administrator account: ");
+               getz(remote_pass, sizeof remote_pass, NULL,     "              Enter the password for this account: ");
 
-               snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
-               sshpid = fork();
-               if (sshpid < 0) {
-                       printf("%s\n", strerror(errno));
-                       cmdexit = errno;
-               }
-               else if (sshpid == 0) {
-                       execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
-                       exit(1);
-               }
-               else {                                          // Wait for SSH to go into the background
-                       waitpid(sshpid, NULL, 0);
+               remote_server_socket = tcp_connectsock(remote_host, remote_port);
+               if (remote_server_socket < 0) {
+                       cmdexit = 1;
                }
        }
 
        if (!cmdexit) {
-               printf("\nTesting a command over the connection...\n\n");
-               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
-                       socket_path, remote_user, remote_host);
-               cmdexit = system(cmd);
-               printf("\n");
-               if (cmdexit != 0) {
-                       printf("Remote commands are not succeeding.\n\n");
+               serv_gets(remote_server_socket, buf);
+               puts(buf);
+               if (buf[0] != '2') {
+                       cmdexit = 1;
                }
        }
 
        if (!cmdexit) {
-               printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
-               snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
-               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
-               cmdexit = system(cmd);
-
-               if (cmdexit) {
-                       snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
-                       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
-                       cmdexit = system(cmd);
+               serv_printf(remote_server_socket, "USER %s\n", remote_user);
+               serv_gets(remote_server_socket, buf);
+               puts(buf);
+               if (buf[0] != '3') {
+                       cmdexit = 1;
                }
+       }
 
-               if (cmdexit) {
-                       printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
-                               "the name of the directory on %s which contains the 'sendcommand' program.\n"
-                               "(example: /opt/foo/citadel)\n"
-                               "--> ", remote_host);
-                       getz(buf);
-                       snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
-                       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
-                       cmdexit = system(cmd);
-                       if (!cmdexit) {
-                               printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
-                       }
+       if (!cmdexit) {
+               serv_printf(remote_server_socket, "PASS %s\n", remote_pass);
+               serv_gets(remote_server_socket, buf);
+               puts(buf);
+               if (buf[0] != '2') {
+                       cmdexit = 1;
                }
        }
 
        if (!cmdexit) {
-               printf("\033[2J\n");
-               printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
-
-               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
-               sourcefp = popen(cmd, "r");
-               if (!sourcefp) {
-                       cmdexit = errno;
-                       printf("\n%s\n\n", strerror(errno));
+               printf( "\033[2J\033[H\n"
+                       "\033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
+                       "║                                                                   ║\n"
+                       "║ \033[33mMigrating from: %-50s\033[32m║\n"
+                       "║                                                                   ║\n"
+                       "╠═══════════════════════════════════════════════════════════════════╣\n"
+                       "║                                                                   ║\n"
+                       "║ Lines received: 0                           Percent complete: 0   ║\n"
+                       "║                                                                   ║\n"
+                       "╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
+                       "\n", remote_host
+               );
+       }
+
+       if (!cmdexit) {
+               serv_puts(remote_server_socket, "MIGR export");
+               serv_gets(remote_server_socket, buf);
+               if (buf[0] != '1') {
+                       printf("\n\033[31m%s\033[0m\n", buf);
+                       cmdexit = 3;
                }
        }
 
@@ -321,7 +410,7 @@ int main(int argc, char *argv[]) {
                serv_puts(local_admin_socket, "MIGR import");
                serv_gets(local_admin_socket, buf);
                if (buf[0] != '4') {
-                       printf("\n%s\n", buf);
+                       printf("\n\033[31m%s\033[0m\n", buf);
                        cmdexit = 3;
                }
        }
@@ -329,16 +418,18 @@ int main(int argc, char *argv[]) {
        if (!cmdexit) {
                char *ptr;
                time_t last_update = time(NULL);
-               while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
+
+               while (serv_gets(remote_server_socket, buf), strcmp(buf, "000")) {
                        ptr = strchr(buf, '\n');
                        if (ptr) *ptr = 0;      // remove the newline character
                        ++linecount;
                        if (!strncasecmp(buf, "<progress>", 10)) {
-                               printf("\033[11;0HPercent complete: \033[32m%d\033[0m\n", atoi(&buf[10]));
+                               progress = atoi(&buf[10]);
+                               printf("\033[8;65H\033[33m%d\033[0m\033[12;0H\n", progress);
                        }
                        if (time(NULL) != last_update) {
                                last_update = time(NULL);
-                               printf("\033[10;0H  Lines received: \033[32m%d\033[0m\n", linecount);
+                               printf("\033[8;19H\033[33m%d\033[0m\033[12;0H\n", linecount);
                        }
                        serv_puts(local_admin_socket, buf);
                }
@@ -346,11 +437,14 @@ int main(int argc, char *argv[]) {
                serv_puts(local_admin_socket, "000");
        }
 
-       // FIXME restart the local server now
+       if ( (cmdexit == 0) && (progress < 100) ) {
+               printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
+               ctdlmigrate_exit(86);
+       }
+
+       // FIXME restart the local server here
 
-       pclose(sourcefp);
-       printf("\nShutting down the socket connection...\n\n");
-       unlink(socket_path);
-       kill(sshpid, SIGKILL);
-       exit(cmdexit);
+       close(remote_server_socket);
+       close(local_admin_socket);
+       ctdlmigrate_exit(cmdexit);
 }