src/vsl_ssh.c

Go to the documentation of this file.
00001 /*
00002  * Copyright 2006, Internet2
00003  * Legal conditions are in file LICENSE
00004  * (MD5 = c434f2e53b8089d8b4d0172c7ce07360).
00005  */
00006 
00007 /**
00008  * @file   vsl_ssh.c
00009  * @author Nikolaus Rath
00010  * @brief  Functions to perform SSH Starting Mode handshake
00011  *
00012  **/
00013 
00014 #include "vsl_ssh.h"
00015 #include "vsl_util.h"
00016 #include "vsl.h"
00017 #include <sys/types.h>
00018 #include <sys/wait.h>
00019 #include <netdb.h>
00020 #include <sys/select.h>
00021 #include <fcntl.h>
00022 
00023 //! message containing the secret
00024 struct ssh_msg_secret {
00025     char magic[P_MAGIC_SIZE];
00026     unsigned char secret[VSL_KEYLEN];
00027 };
00028 
00029 //! message containing the port
00030 struct ssh_msg_port {
00031     char magic[P_MAGIC_SIZE];
00032     uint32_t port;
00033 };
00034 
00035 /**
00036  * Fork ssh process to connect to remote host and and start
00037  * server process.
00038  *
00039  * Stores pid and stdin of the ssh process in the socket.
00040  *
00041  * @param[in] sock VSL socket to use
00042  * @param[in] host hostname to connect to
00043  * @param[in] user username for SSH connection, \c NULL to use current user
00044  * @param[in] cmd  program name of the server application
00045  * @param[in] argc  number of arguments in \a argv that are passed to the server application
00046  * @param[in] argv  arguments that is passed to the server application
00047  * @return
00048  *   - 0 on success
00049  *   - #VSL_ERRNO for all other errors (errno is set accordingly)
00050  **/
00051 int ssh_fork(vsl_sock* sock, const char* host, const char* user,
00052              const char* cmd, int argc, const char* const* argv) {
00053 
00054     LOG("attempting to fork ssh...");
00055 
00056     /* Create pipes */
00057     int stdin_fd[2],
00058         stderr_fd[2],
00059         stdout_fd[2];
00060     if ( pipe(stdin_fd) == -1 ||
00061          pipe(stderr_fd) == -1 ||
00062          pipe(stdout_fd) == -1)
00063         LOGFAIL(VSL_ERRNO, "pipe failed: %s", strerror(err));
00064 
00065     /* Fork & Exec */
00066     pid_t pid;
00067     if ( (pid = fork()) == 0 ) {
00068 
00069         // Connect pipe to stdin
00070         if ( close(stdin_fd[1]) != 0)
00071             LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00072         if ( stdin_fd[0] != STDIN_FILENO ) {
00073             if ( dup2(stdin_fd[0], STDIN_FILENO) != STDIN_FILENO ) {
00074                 LOG("(child) dup2 failed: %s!", strerror(errno));
00075                 exit(VSL_TEMP);
00076             }
00077             if ( close(stdin_fd[0]) != 0)
00078                 LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00079         }
00080 
00081         // Connect pipe to stdout
00082         if ( close(stdout_fd[0]) != 0)
00083             LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00084         if ( stdout_fd[1] != STDOUT_FILENO) {
00085             if ( dup2(stdout_fd[1], STDOUT_FILENO) != STDOUT_FILENO ) {
00086                 LOG("(child) dup2 failed: %s", strerror(errno));
00087                 exit(VSL_TEMP);
00088             }
00089             if ( close(stdout_fd[1]) != 0)
00090                 LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00091         }
00092 
00093         // Connect pipe to stderr
00094         if ( close(stderr_fd[0]) != 0)
00095             LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00096         if ( stderr_fd[1] != STDERR_FILENO ) {
00097             if ( dup2(stderr_fd[1], STDERR_FILENO) != STDERR_FILENO ) {
00098                 LOG("(child) dup2 failed: %s", strerror(errno));
00099                 exit(VSL_TEMP);
00100             }
00101             if ( close(stderr_fd[1]) != 0)
00102                 LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00103         }
00104 
00105         // Call ssh
00106         const char *tot_argv[7 + argc];
00107         int cur = 0;
00108         tot_argv[cur++] = "ssh";
00109         tot_argv[cur++] = "-T";
00110         if (user != NULL) {
00111             tot_argv[cur++] = "-l";
00112             tot_argv[cur++] = user;
00113         }
00114         tot_argv[cur++] = host;
00115         tot_argv[cur++] = cmd;
00116         for  (int i=0; i < argc; i++)
00117             tot_argv[cur++] = argv[i];
00118         tot_argv[cur++] = NULL;
00119 
00120         LOG("(child) exec'ing ssh...");
00121         execvp("ssh", (char *const*) tot_argv);
00122         LOG("(child) exec failed: %s", strerror(errno));
00123         exit(VSL_PERM);
00124     }
00125 
00126     else if(pid == -1)
00127         LOGFAIL(VSL_ERRNO, "fork failed: %s", strerror(err));
00128 
00129     /* close fds */
00130     if ( close(stdin_fd[0]) != 0 ||
00131          close(stdout_fd[1]) != 0 ||
00132          close(stderr_fd[1]) != 0 )
00133         LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00134     sock->ssh_pid = pid;
00135     sock->ssh_stdin_fd = stdin_fd[1];
00136     sock->ssh_stdout_fd = stdout_fd[0];
00137     sock->ssh_stderr_fd = stderr_fd[0];
00138 
00139     /* Set Nonblocking Flags */
00140     if (!is_blocking(sock)) {
00141         long fdflags;
00142         if ( (fdflags = fcntl(sock->ssh_stdin_fd, F_GETFL, 0)) < 0 ||
00143              fcntl(sock->ssh_stdin_fd, F_SETFL, fdflags | O_NONBLOCK) < 0 ||
00144              (fdflags = fcntl(sock->ssh_stdout_fd, F_GETFL, 0)) < 0 ||
00145              fcntl(sock->ssh_stdout_fd, F_SETFL, fdflags | O_NONBLOCK) < 0 ||
00146              (fdflags = fcntl(sock->ssh_stderr_fd, F_GETFL, 0)) < 0 ||
00147              fcntl(sock->ssh_stderr_fd, F_SETFL, fdflags | O_NONBLOCK) < 0 )
00148             LOGFAIL(VSL_ERRNO, "fcntl failed: %s", strerror(err));
00149     }
00150 
00151 
00152     LOG("ssh forked successfully.");
00153     return(0);
00154 }
00155 
00156 /**
00157  * Sends the secret over the ssh connection.
00158  *
00159  * \param[in] sock socket to use
00160  * \param[in] secret secret to send (length VSL_KEYLENGTH)
00161  * \return
00162  *  - 0 on success
00163  *  - #VFER_INPROGRESS if the socket is nonblocking and the call would
00164  *    block.
00165  *  - #VSL_ERRNO for all other errors (errno is set accordingly)
00166  **/
00167 int ssh_send_secret (vsl_sock* sock, const unsigned char* secret) {
00168     LOG("attempting to send shared secret over ssh...");
00169     LOG("secret is " KEY_STR, KEY_VEC(secret));
00170 
00171     /* Create message */
00172     struct ssh_msg_secret msg;
00173     FILL_MAGIC(msg.magic);
00174     memcpy((void *) msg.secret, (void*) secret, VSL_KEYLEN);
00175 
00176     /* Serialize */
00177     size_t buflen;
00178     void *buf;
00179     if ( (buf = write_objs(&buflen,
00180                            (void*) &msg.magic, sizeof(msg.magic),
00181                            (void*) &msg.secret, sizeof(msg.secret),
00182                            NULL)) == NULL)
00183         LOGFAIL(VSL_ERRNO, "malloc failed: %s", strerror(err));
00184 
00185     /* Ignore SIGPIPE */
00186     if ( signal(SIGPIPE, SIG_IGN) == SIG_ERR )
00187         LOGFATAL("cannot ignore sigpipe!");
00188 
00189     /* Send secret */
00190     if(write(sock->ssh_stdin_fd, buf, buflen) != buflen) {
00191          free(buf);
00192          if(!is_blocking(sock) &&
00193             errno == EAGAIN)
00194              return(VFER_INPROGRESS);
00195          LOGFAIL(VSL_ERRNO, "write failed: %s", strerror(err));
00196     }
00197     free(buf);
00198 
00199     /* Close fd */
00200     if ( close(sock->ssh_stdin_fd) != 0 )
00201         LOGFAIL(VSL_ERRNO, "close failed: %s", strerror(err));
00202 
00203     LOG("shared secret sent over ssh.");
00204 
00205     return(0);
00206 }
00207 
00208 /**
00209  * Read port number from ssh process
00210  *
00211  * \param[in] sock socket to use
00212  * \param[out] port read port
00213  * \return
00214  *  - 0 on success
00215  *  - #VFER_INPROGRESS if the socket is nonblocking and the call would
00216  *    block.
00217  *  - #VSL_BADPROT in case of a protocol error
00218  *  - #VSL_ERRNO for all other errors (errno is set accordingly)
00219  **/
00220 int ssh_read_port (vsl_sock* sock, int* port) {
00221     LOG("attempting to get port number...");
00222 
00223     /* Try to read a message */
00224     ssize_t ret;
00225     struct ssh_msg_port msg;
00226     unsigned char buf[sizeof msg];
00227     if ( (ret = read(sock->ssh_stdout_fd, (void*) buf, sizeof(msg))) < 0) {
00228         if (errno == EAGAIN)
00229             return VFER_INPROGRESS;
00230         else
00231             LOGFAIL(VSL_ERRNO, "read failed: %s", strerror(errno));
00232     }
00233 
00234     /* Deserialize and verify structure */
00235     if ( read_objs(buf, ret,
00236                    (void*) &msg.magic, sizeof(msg.magic),
00237                    (void*) &msg.port, sizeof(msg.port),
00238                    NULL) == NULL )
00239         LOGFAIL(VSL_BADPROT, "received message too short!");
00240 
00241     if(!CHECK_MAGIC(msg.magic))
00242         LOGFAIL(VSL_BADPROT, "invalid message received");
00243     *port = ntohl(msg.port);
00244 
00245     LOG("read port nr. %d.", *port);
00246     return 0;
00247 }
00248 
00249 /**
00250  * Close ssh process
00251  *
00252  * \param[in] sock socket to use
00253  * \param[out] errmsg if the ssh process returned an error message, it
00254  *   is written into this variable, which has to have a minimum length
00255  *   of 513 bytes.
00256  * \return
00257  *  - 0 on success
00258  *  - #VFER_INPROGRESS if the socket is nonblocking and the call would
00259  *    block.
00260  *  - #VSL_SSHF if the ssh process failed
00261  *  - #VSL_TEMP if the server signaled a temporary error
00262  *  - #VSL_PERM if the server signaled a permanent error
00263  *  - #VSL_BADPROT in case of a protocol error
00264  *  - #VSL_ERRNO for all other errors (errno is set accordingly)
00265  **/
00266 int ssh_close (vsl_sock* sock, char* errmsg)  {
00267     int retval = 0;
00268 
00269     LOG("attempting to read error message...");
00270 
00271     /* Try to read an error message */
00272     int ret;
00273     if ( (ret = read(sock->ssh_stderr_fd, (void*) errmsg, 512)) != 0 ) {
00274         if (ret == -1 &&
00275             errno == EAGAIN) {
00276             LOG("call would block, returning.");
00277             return VFER_INPROGRESS;
00278         }
00279         else if (ret == -1)
00280             LOGFAIL(VSL_ERRNO, "read failed: %s", strerror(errno));
00281         errmsg[ret] = '\0';
00282         LOG("got error message: %s.", errmsg);
00283         retval = VSL_SSHF;
00284     }
00285 
00286     /* close fds */
00287     /** \todo This close call often fails with 'Bad file descriptor!'
00288      *  -- ist that normal behaviour if the program has quit and there's
00289      * no data on the pipe?
00290      */
00291     if ( close(sock->ssh_stdin_fd) != 0 ||
00292          close(sock->ssh_stdout_fd) != 0 ||
00293          close(sock->ssh_stderr_fd) != 0 )
00294         //LOGFAIL(VSL_ERRNO, "can't close pipe: %s!", strerror(errno));
00295         ;
00296 
00297 
00298     // We migth have wait()ed already in vsl_select
00299     int pidstat;
00300     if ( sock->ssh_pid == 0 ) {
00301         LOG("using existing ssh exit status...");
00302         pidstat = sock->ssh_pid_stat;
00303     }
00304     else {
00305         /* wait for process */
00306         LOG("attempting to wait for ssh process...");
00307         if(!is_blocking(sock)) {
00308             if( (ret=waitpid(sock->ssh_pid, &pidstat, WNOHANG)) == 0) {
00309                 LOG("call would block, returning.");
00310                 return VFER_INPROGRESS;
00311             }
00312         }
00313         else
00314             ret = waitpid(sock->ssh_pid, &pidstat, 0);
00315 
00316         if(ret != sock->ssh_pid)
00317             LOGFAIL(VSL_ERRNO, "waitpid failed: %s", strerror(err));
00318     }
00319 
00320     /* Exit code? */
00321     if( !WIFEXITED(pidstat) )
00322         LOGFAIL(VSL_SSHF, "ssh process aborted.");
00323 
00324     if(WEXITSTATUS(pidstat) == 255) {
00325         LOGFAIL(VSL_SSHF, "ssh process failed!");
00326     }
00327     else if (WEXITSTATUS(pidstat) == VSL_TEMP ||
00328              WEXITSTATUS(pidstat) == VSL_PERM ) {
00329         LOGFAIL(WEXITSTATUS(pidstat), "server signaled error!");
00330     }
00331     else if(WEXITSTATUS(pidstat) != 0 )
00332         LOGFAIL(VSL_BADPROT, "invalid exit code!");
00333 
00334     LOG("ssh connection closed.");
00335     return retval;
00336 }
00337 
00338 
00339 /**
00340  * Establish a VFER connection to the given host
00341  *
00342  * @param[in] sock VSL socket to use
00343  * @param[in] host hostname to connect to
00344  * @param[in] port port to connect to
00345  * @return
00346  *   - 0 on success
00347  *   - #VSL_ERRNO for all other errors (errno is set accordingly)
00348  **/
00349 int ssh_vfer_connect(vsl_sock* sock, const char* host, int port) {
00350 
00351     LOG("attempting vfer_connect to %s:%d...", host, port);
00352     int ret;
00353 
00354     // setup sockaddr structure for client
00355     struct sockaddr_in client_sa;
00356     bzero((char*)(&client_sa), sizeof(client_sa));
00357     client_sa.sin_port = htons(0);
00358     client_sa.sin_family = AF_INET;
00359 
00360     // bind the socket to a local addr
00361     if ( (ret=vfer_bind(sock->vfd, (struct sockaddr*) &client_sa, sizeof(client_sa))) < 0)
00362         LOGFATAL("vfer_bind failed [%s]", vfer_errortext(ret));
00363 
00364     /* get the remote ip */
00365     char remote_ip[INET_ADDRSTRLEN];
00366     struct hostent* h;
00367     if ((h = gethostbyname(host)) == NULL)
00368         LOGFAIL(VSL_ERRNO, "can't get remote ip: %s", strerror(errno));
00369     if (h->h_addr_list != 0) {
00370         inet_ntop(h->h_addrtype, (h->h_addr_list)[0], remote_ip, sizeof(remote_ip));
00371     }
00372 
00373     /* setup sockaddr structure for server */
00374     struct sockaddr_in server_sa;
00375     bzero((char*)(&server_sa), sizeof(server_sa));
00376     server_sa.sin_family = AF_INET;
00377     server_sa.sin_port = htons(port);
00378     if (inet_pton(AF_INET, remote_ip, &server_sa.sin_addr) != 1)
00379         LOGFAIL(VSL_ERRNO, "inet_pton failed: %s", strerror(errno));
00380 
00381     /* connect on the socket */
00382     //! \todo Disabled, for debbugging we use blocking IO.
00383      /*
00384     bool blocking = is_blocking(sock);
00385     set_blocking(sock, false);
00386     if ((ret = vfer_connect(sock->vfd, (struct sockaddr*) &server_sa,
00387                             sizeof(server_sa))) != VFER_INPROGRESS)
00388         LOGFATAL("vfer_connect failed [%s]", vfer_errortext(ret));
00389     set_blocking(sock, blocking);
00390     LOG("connection continues in background, returning.");
00391     */
00392     // /*
00393     if ((ret = vfer_connect(sock->vfd, (struct sockaddr*) &server_sa,
00394                             sizeof(server_sa))) != 0 &&
00395         ret != VFER_INPROGRESS )
00396         LOGFATAL("vfer_connect failed [%s]", vfer_errortext(ret));
00397     LOG("connection established.");
00398     // */
00399 
00400     return 0;
00401 }
00402 
00403 /**
00404  * Reads the secret from stdin and stores it in the socket.
00405  *
00406  * @param[out] secret variable to store secret in, minimum length
00407  * #VSL_KEYLEN
00408  *
00409  * \return
00410  *  - 0 on success
00411  *  - #VSL_BADPROT in case of a protocol error
00412  *  - #VSL_ERRNO if a system call failed (errno is set accordingly)
00413  **/
00414 int ssh_read_secret (unsigned char* secret) {
00415     LOG("attempting to get secret...");
00416 
00417     /* Try to read a message */
00418     ssize_t ret;
00419     struct ssh_msg_secret msg;
00420     unsigned char buf[sizeof msg];
00421     if ( (ret = read(STDIN_FILENO, (void*) buf, sizeof(msg))) < 0) {
00422         if (errno == EAGAIN)
00423             return VFER_INPROGRESS;
00424         else
00425             LOGFAIL(VSL_ERRNO, "read failed: %s", strerror(errno));
00426     }
00427 
00428     /* Deserialize and verify structure */
00429     if ( read_objs(buf, ret,
00430                    (void*) &msg.magic, sizeof(msg.magic),
00431                    (void*) &msg.secret, sizeof(msg.secret),
00432                    NULL) == NULL )
00433         LOGFAIL(VSL_BADPROT, "received message too short!");
00434 
00435     if(!CHECK_MAGIC(msg.magic))
00436         LOGFAIL(VSL_BADPROT, "invalid message received!");
00437     memcpy((void*) secret, (void*) msg.secret, VSL_KEYLEN);
00438 
00439     LOG("got secret " KEY_STR ".", KEY_VEC(secret));
00440     return 0;
00441 }
00442 
00443 
00444 
00445 /**
00446  * Configures the socket to listen on a free port and returns that
00447  * port.
00448  *
00449  * @param[in] sock socket to use
00450  * \param[out] port reserved port
00451  *
00452  * \return
00453  *  - 0 on success
00454  *  - #VSL_ERRNO if a system call failed (errno is set accordingly)
00455  **/
00456 int ssh_vfer_listen (vsl_sock* sock, int* port) {
00457     LOG("attempting to listen...");
00458 
00459     struct sockaddr_in sa;
00460     socklen_t len = sizeof(sa);
00461     int ret;
00462 
00463     bzero((char*)(&sa), sizeof(sa));
00464     sa.sin_family = AF_INET;
00465     sa.sin_addr.s_addr = htonl(INADDR_ANY);
00466     sa.sin_port = htons(0);
00467 
00468     if ((ret = vfer_bind(sock->vfd, (struct sockaddr*) &sa, sizeof(sa))) != 0)
00469         LOGFATAL("vfer_bind failed: %s!", vfer_errortext(ret));
00470 
00471     if ((ret = vfer_listen(sock->vfd, 3)) != 0)
00472         LOGFATAL("vfer_listen failed: %s!", vfer_errortext(ret));
00473 
00474     if ((ret = vfer_getsockname(sock->vfd, (struct sockaddr*) &sa, &len)) != 0)
00475         LOGFATAL("vfer_getsockname failed: %s!", vfer_errortext(ret));
00476 
00477     *port = ntohs(sa.sin_port);
00478 
00479     LOG("listening on port %d.", *port);
00480     return 0;
00481 }
00482 
00483 
00484 /**
00485  * Prints the given port
00486  *
00487  * \param[in] port port
00488  *
00489  * \return
00490  *  - 0 on success
00491  *  - #VSL_ERRNO if a system call failed (errno is set accordingly)
00492  **/
00493 int ssh_send_port (int port) {
00494     LOG("attempting to send port %d...", port);
00495 
00496     /* Create message */
00497     struct ssh_msg_port msg;
00498     FILL_MAGIC(msg.magic);
00499     msg.port = htonl(port);
00500 
00501     /* Serialize */
00502     size_t buflen;
00503     void *buf;
00504     if ( (buf = write_objs(&buflen,
00505                            (void*) &msg.magic, sizeof(msg.magic),
00506                            (void*) &msg.port, sizeof(msg.port),
00507                            NULL)) == NULL)
00508         LOGFAIL(VSL_ERRNO, "malloc failed: %s", strerror(err));
00509 
00510     /* Send key */
00511     if(write(STDOUT_FILENO, (void*) buf, buflen) != buflen) {
00512          free(buf);
00513          LOGFAIL(VSL_ERRNO, "write failed: %s", strerror(errno));
00514     }
00515     free(buf);
00516 
00517     LOG("port message sent.");
00518     return 0;
00519 }
00520 
00521 
00522 /**
00523  * Waits for a vfer connection and closes the listening socket.
00524  *
00525  * @param[in] sock socket to use
00526  *
00527  * \return
00528  *  - 0 on success
00529  *  - #VFER_TIMEOUT a timeout occured
00530  **/
00531 int ssh_vfer_accept (vsl_sock* sock) {
00532     LOG("attempting to accept vfer connection...");
00533 
00534     /* Set timeout */
00535     int ret;
00536     if ( (ret = vfer_setsockopt(sock->vfd, VFERSO_ACCEPTTIMEOUT,
00537                                 &((int) { 60000000 } ), sizeof(int))) != 0)
00538         LOGFATAL("can't set socket state: %s!", vfer_errortext(ret));
00539 
00540     /* Accept connection */
00541     struct sockaddr_in sa;
00542     bzero((char*)(&sa), sizeof(sa));
00543     socklen_t len = sizeof(sa);
00544     int vfd;
00545     if ((vfd = vfer_accept(sock->vfd, (struct sockaddr*) &sa, &len)) == VFER_TIMEOUT)
00546         LOGFAIL(vfd, "timed out, returning.");
00547     else if ( vfd < 0 )
00548         LOGFATAL("vfer_accept failed: %s!", vfer_errortext(vfd));
00549     sock->vfd = vfd;
00550 
00551     LOG("Done.");
00552     return 0;
00553 }
00554 
00555 /**
00556  * Daemonizes the process
00557  *
00558  * \return
00559  *  - 0 on success
00560  *  - #VSL_ERRNO if a system call failed (errno is set accordingly)
00561  **/
00562 int ssh_daemonize (void) {
00563     LOG("daemonizing...");
00564     if (daemon(0,0) != 0 )
00565         LOGFAIL(VSL_ERRNO, "daemon failed: %s!", strerror(errno));
00566     return 0;
00567 }
00568 
00569 
00570 /*
00571  * Local Variables:
00572  * compile-command: "cd ..; make tests file_xfer"
00573  * compilation-search-path: ("..")
00574  * End:
00575  */

Generated on Tue Aug 8 16:07:20 2006 for VFER by  doxygen 1.4.7