/* Copyright 2009 Nathan Phillip Brink This file is a part of DistRen. DistRen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DistRen 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with DistRen. If not, see . */ #include "libremoteio.h" #include "execio.h" #include "asprintf.h" #include #include #include #include #include #include #include #include #ifndef WINDOWS #include #endif /* local */ #define REMOTEIO_DEFAULT_PORT "4050" int _remoteio_ssh_open(struct remoteio *rem, struct remoteio_server *server); int _remoteio_ssh_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread); int _remoteio_ssh_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten); int _remoteio_ssh_close(struct remoteio *rem); #ifndef WINDOWS int _remoteio_sock_open(struct remoteio *rem, struct remoteio_server *server); int _remoteio_sock_close(struct remoteio *rem); #endif int _remoteio_sock_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread); int _remoteio_sock_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten); int _remoteio_tcp_open(struct remoteio *rem, struct remoteio_server *server); int _remoteio_tcp_close(struct remoteio *rem); /** lookup table for different methods of remoteio: the enum remoteio_method is the index of the entry to use for that method. Regardless, a NULL terminator is required because the configuration function searches through this table for the method specified in the config file. */ struct remoteio_method_funcmap funcmap[] = { /* [REMOTEIO_METHOD_SSH] */ {REMOTEIO_METHOD_SSH, &_remoteio_ssh_open, &_remoteio_ssh_read, &_remoteio_ssh_write, &_remoteio_ssh_close, "ssh"}, #ifndef WINDOWS {REMOTEIO_METHOD_UNIX, &_remoteio_sock_open, &_remoteio_sock_read, &_remoteio_sock_write, &_remoteio_sock_close, "unix"}, #endif {REMOTEIO_METHOD_TCP, &_remoteio_tcp_open, &_remoteio_sock_read, &_remoteio_sock_write, &_remoteio_tcp_close, "tcp"}, {REMOTEIO_METHOD_MAX, NULL, NULL, NULL, NULL, NULL} }; struct remoteio_server *remoteio_getserver(const struct remoteio_opts *opts, const char *servername); int remoteio_config(cfg_t *cfg, struct remoteio_opts *opts) { size_t numservers; size_t counter; static int haslisted_methods = 0; struct remoteio_server *aserver; opts->servers = malloc(sizeof(struct remoteio_server)); if(!opts->servers) { fprintf(stderr, "@todo cleanup!\n"); abort(); } aserver = opts->servers; numservers = cfg_size(cfg, "server"); for(counter = 0; counter < numservers; counter ++) { cfg_t *cfg_aserver; char *method; cfg_aserver = cfg_getnsec(cfg, "server", counter); if(!aserver) /*< if the malloc in the previous loop failed */ abort(); aserver->name = strdup(cfg_title(cfg_aserver)); aserver->hostname = strdup(cfg_getstr(cfg_aserver, "hostname")); aserver->username = strdup(cfg_getstr(cfg_aserver, "username")); aserver->method = REMOTEIO_METHOD_MAX; method = cfg_getstr(cfg_aserver, "method"); for(counter = 0; funcmap[counter].name; counter ++) if(strcmp(method, funcmap[counter].name) == 0) aserver->method = REMOTEIO_METHOD_SSH; if(aserver->method == REMOTEIO_METHOD_MAX) { fprintf(stderr, "No such method as %s\n", method); if(!haslisted_methods) { fprintf(stderr, "Available methods:\n"); for(counter = 0; funcmap[counter].name; counter ++) fprintf(stderr, "\t%s\n", funcmap[counter].name); haslisted_methods ++; } abort(); } if(counter < numservers - 1) { aserver->next = malloc(sizeof(struct remoteio_server)); aserver = aserver->next; } } aserver->next = NULL; return 0; } int remoteio_open(struct remoteio **remoteio, struct remoteio_opts *opts, const char *servername) { struct remoteio_server *theserver; struct remoteio *rem; int tmp; if(!opts) { fprintf(stderr, "%s:%d: no null opts!\n\tThis is a bug, please report it (after making sure it isn't already reported)\n", __FILE__, __LINE__); return 1; } theserver = remoteio_getserver(opts, servername); if(!theserver) { fprintf(stderr, "%s:%d: Could not find server named ``%s''\n", __FILE__, __LINE__, servername); return 1; } if(theserver->method >= REMOTEIO_METHOD_MAX || theserver->method < 0) { fprintf(stderr, "%s:%d: Unsupported remoteio method %d\n\tThis is a bug, probably indicating memory corruption. This is, of course, probably my fault (not your hardware's) ;-)\n", __FILE__, __LINE__, theserver->method); return 1; } rem = malloc(sizeof(struct remoteio)); if(!rem) { fprintf(stderr, "OOM\n"); return 2; } *remoteio = rem; rem->method = theserver->method; rem->opts = opts; tmp = funcmap[theserver->method].open_func(rem, theserver); if(tmp) { fprintf(stderr, "Error using method %s for server ``%s''", funcmap[theserver->method].name, servername); free(rem); *remoteio = NULL; return tmp; } return 0; } int remoteio_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread) { return funcmap[rem->method].read_func(rem, buf, len, bytesread); } int remoteio_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten) { return funcmap[rem->method].write_func(rem, buf, len, byteswritten); } int remoteio_close(struct remoteio *rem) { int rtn; rtn = funcmap[rem->method].close_func(rem); free(rem); return rtn; } struct remoteio_server *remoteio_getserver(const struct remoteio_opts *opts, const char *servername) { struct remoteio_server *aserver; for(aserver = opts->servers; aserver; aserver = aserver->next) if(!strcmp(servername, aserver->name)) return aserver; return (struct remoteio_server *)NULL; } /** different remoteio methods' implementations: */ /* SSH, via execio */ int _remoteio_ssh_open(struct remoteio *rem, struct remoteio_server *server) { char *userhost; char *sshargs[] = {rem->opts->ssh_command, NULL /* userhost */, "distrend", "-d", (char *)NULL}; int rtn; if(server->username) _distren_asprintf(&userhost, "%s@%s", server->username, server->hostname); else userhost = strdup(server->hostname); sshargs[1] = userhost; rtn = execio_open( &rem->execio, "ssh", sshargs); if(rtn) { fprintf(stderr, "error opening remoteio channel to ssh userhost ``%s''\n" , userhost); free(userhost); return 1; } free(userhost); return 0; } int _remoteio_ssh_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread) { return execio_read(rem->execio, buf, len, bytesread); } int _remoteio_ssh_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten) { return execio_write(rem->execio, buf, len, byteswritten); } int _remoteio_ssh_close(struct remoteio *rem) { int rtn; rtn = execio_close(rem->execio); if(rtn) fprintf(stderr, "%s:%d: error closing execio\n", __FILE__, __LINE__); return rtn; } #ifndef WINDOWS /* local sockets implementation (``named pipes''), unix-only */ int _remoteio_sock_open(struct remoteio *rem, struct remoteio_server *server) { int sock; struct sockaddr_un sockaddr; /* The POSIX docs pretty much say that I can't depend on sockpath being able to be longer than some proprietary length. So, if the compiler specifies a long path for RUNSTATEDIR, it could cause a buffer overflow. */ char *sockpath = RUNSTATEDIR "/distrend.sock"; unsigned int sockaddr_len; sock = socket(AF_UNIX, SOCK_STREAM, 0); if(sock == -1) { perror("socket"); return 1; } sockaddr.sun_family = AF_UNIX; /* The terminating NULL should not be included in what's copied to sun_path, although it won't hurt as long as strlen(sockpath) < max socket length */ for(sockaddr_len = 0; sockpath[sockaddr_len]; sockaddr_len ++) sockaddr.sun_path[sockaddr_len] = sockpath[sockaddr_len]; if(connect(sock, (struct sockaddr *)&sockaddr, sockaddr_len) == -1) { perror("connect"); close(sock); return 1; } rem->sock = sock; return 0; } int _remoteio_sock_close(struct remoteio *rem) { close(rem->sock); return 0; } #endif int _remoteio_sock_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread) { ssize_t readrtn; readrtn = read(rem->sock, buf, len); /* The following is valid for blocking sockets: */ if(readrtn == -1) { /* in this case, we may have been interrupted by a signal and errno == EINTR or the connection was reset and errno = ECONNRESET Some of these are not error conditions: */ perror("read"); *bytesread = 0; if(errno != EINTR) { fprintf(stderr, "error reading socket in remoteio_sock_read\n"); return 1; } return 0; } *bytesread = readrtn; if(!readrtn) { /* means EOF except when FD is in ``message-nondiscard'' or ``message-discard'' modes. */ return 1; } return 0; } int _remoteio_sock_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten) { ssize_t writertn; writertn = write(rem->sock, buf, len); if(writertn == -1) { perror("write"); if(errno != EINTR) { fprintf(stderr, "error writing to socket in remoteio_sock_write()\n"); return 1; } } *byteswritten = writertn; if(!writertn) { /* should we consider this an error? I'm pretty sure we should :-/ */ fprintf(stderr, "write() returned 0 in remoteio_sock_write()\n"); return 1; } return 0; } /** TCP, taking advantage of the two generic socks read/write functions: */ int _remoteio_tcp_open(struct remoteio *rem, struct remoteio_server *server) { int tmp; int tmp2; int sock; char *hostname; char *port; struct addrinfo addrinfo_hints; struct addrinfo *addrinfo_res; /** only hostname should be free()-ed, not port, because both are from the same block of malloc()-ed memory */ hostname = strdup(server->hostname); for(port = hostname; *port && *port != ':'; port ++) ; if(*port) { *port = '\0'; port ++; } else port = REMOTEIO_DEFAULT_PORT; memset(&addrinfo_hints, '\0', sizeof(struct addrinfo)); addrinfo_hints.ai_family = AF_UNSPEC; addrinfo_hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; addrinfo_hints.ai_socktype = SOCK_STREAM; tmp = getaddrinfo(server->hostname, port, &addrinfo_hints, &addrinfo_res); if(tmp) fprintf(stderr, "error resolving %s:%s: %s\n", server->hostname, port, gai_strerror(tmp)); fprintf(stderr, "connecting to %s[%s]:%s\n", server->hostname, addrinfo_res->ai_canonname, port); free(hostname); sock = socket(addrinfo_res->ai_family, SOCK_STREAM, addrinfo_res->ai_protocol); if(sock == -1) { perror("socket"); freeaddrinfo(addrinfo_res); return 1; } tmp = connect(sock, addrinfo_res->ai_addr, addrinfo_res->ai_addrlen); tmp2 = errno; freeaddrinfo(addrinfo_res); errno = tmp2; if(tmp == -1) { perror("connect"); close(sock); return 1; } rem->sock = sock; return 0; } int _remoteio_tcp_close(struct remoteio *rem) { close(rem->sock); return 0; }