]> code.citadel.org Git - citadel.git/blobdiff - citadel/ctdlmigrate.c
My current love affair is with C++ style comments. Will it last?
[citadel.git] / citadel / ctdlmigrate.c
index 78af852bd6bdf80349daae4a4b30af0910a80c23..b2f58b80afddb245a9bbe60eacd3fc8e1b9631a0 100644 (file)
@@ -28,6 +28,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>
 
 
 
+// yeah, this wouldn't work in a multithreaded program.
+static int gmaxlen = 0;
+static char *gdeftext = NULL;
+
+static int limit_rl(FILE *dummy) {
+       if (rl_end > gmaxlen) {
+               return '\b';
+       }
+       return rl_getc(dummy);
+}
+
+
+static int getz_deftext(void) {
+       if (gdeftext) {
+               rl_insert_text(gdeftext);
+               gdeftext = NULL;
+               rl_startup_hook = NULL;
+       }
+       return 0;
+}
+
+
 /*
  * 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;
+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));
+}
+
+
+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;
 
-       ptr = fgets(buf, SIZ, stdin);
-       if (!ptr) {
-               buf[0] = 0;
-               return;
+       /*
+        * 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;
+               }
        }
 
-       ptr = strchr(buf, '\n');
-       if (ptr) *ptr = 0;
+       /* 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);
+               }
+
+               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);
 }
 
 
@@ -68,14 +161,14 @@ 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;
@@ -152,21 +245,37 @@ void serv_puts(int serv_sock, char *buf) {
 }
 
 
+/*
+ * serv_printf()       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);
+}
+
+
 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 */
        while ((a = getopt(argc, argv, "h:")) != EOF) {
@@ -175,49 +284,69 @@ int main(int argc, char *argv[]) {
                        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"
+               "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.  You will also need\n"
-               "the 'rsync' utility, and OpenSSH v4 or newer.\n"
+               "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\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 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 +364,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 +431,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 +439,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;75H\033[33m%d\033[0m\033[20;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;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
                        }
                        serv_puts(local_admin_socket, buf);
                }
@@ -346,11 +458,12 @@ int main(int argc, char *argv[]) {
                serv_puts(local_admin_socket, "000");
        }
 
+       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 now
 
-       pclose(sourcefp);
-       printf("\nShutting down the socket connection...\n\n");
-       unlink(socket_path);
-       kill(sshpid, SIGKILL);
-       exit(cmdexit);
+       ctdlmigrate_exit(cmdexit);
 }