#include #include #include #include #include #include #include #include #include #include #include #include #include "connection.h" #include "setup.h" #if DEBUG == 1 #define DEBUG_PRINTF(args...) fprintf(stderr, args) #elif DEBUG == 2 #define DEBUG_PRINTF(args...) dsyslog(args) #else #define DEBUG_PRINTF(args...) #endif int charsetcmp(const char* s, const char* t) { int ret; do { while (*s && !isalnum(*s)) s++; while (*t && !isalnum(*t)) t++; ret = toupper(*t) - toupper(*s); } while (ret == 0 && *(t++) && *(s++)); return ret; } // cSvdrpConnection ------------------------------------------------------- int cSvdrpConnection::Connect() { if (!serverIp) { esyslog("svdrpservice: No server IP specified"); return -1; } struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(serverPort); if (!::inet_aton(serverIp, &server_addr.sin_addr)) { esyslog("svdrpservice: Invalid server IP '%s'", serverIp); return -1; } int sock = ::socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { esyslog("svdrpservice: Error creating socket for connection to %s: %m", serverIp); return -1; } // set nonblocking int flags = ::fcntl(sock, F_GETFL, 0); if (flags < 0 || ::fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { esyslog("svdrpservice: Unable to use nonblocking I/O for %s: %m", serverIp); return -1; } if (::connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { if (errno != EINPROGRESS) { esyslog("svdrpservice: connect to %s:%hu failed: %m", serverIp, serverPort); return -1; } int result; fd_set fds; struct timeval tv; cTimeMs starttime; int timeout = SvdrpServiceSetup.connectTimeout * 1000; do { FD_ZERO(&fds); FD_SET(sock, &fds); tv.tv_usec = (timeout % 1000) * 1000; tv.tv_sec = timeout / 1000; result = ::select(sock + 1, NULL, &fds, NULL, &tv); } while (result == -1 && errno == EINTR && (timeout = SvdrpServiceSetup.connectTimeout * 1000 - starttime.Elapsed()) > 100); if (result == 0) { // timeout result = -1; errno = ETIMEDOUT; } else if (result == 1) { // check socket for errors int error; socklen_t size = sizeof(error); result = ::getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &size); if (result == 0 && error != 0) { result = -1; errno = error; } } if (result != 0) { esyslog("svdrpservice: Error connecting to %s:%hu: %m", serverIp, serverPort); ::close(sock); return -1; } } return sock; } cSvdrpConnection::cSvdrpConnection(const char *ServerIp, unsigned short ServerPort, bool Shared): serverPort(ServerPort), convIn(NULL), convOut(NULL), refCount(0), shared(Shared) { serverIp = ServerIp ? ::strdup(ServerIp) : NULL; bufSize = BUFSIZ; buffer = MALLOC(char, bufSize); } cSvdrpConnection::~cSvdrpConnection(void) { Close(); delete convIn; delete convOut; free(serverIp); free(buffer); } bool cSvdrpConnection::HasDestination(const char *ServerIp, unsigned short ServerPort) const { if (ServerIp == NULL || serverIp == NULL) return false; return serverPort == ServerPort && !strcmp(serverIp, ServerIp); } bool cSvdrpConnection::Open() { if (file.IsOpen()) { //TODO: make sure the connection is still alive (e.g. send STAT command) } if (!file.IsOpen()) { int fd = Connect(); if (fd < 0) return false; if (!file.Open(fd)) { ::close(fd); return false; } // check for greeting cList greeting; if (Receive(&greeting, true) != 220) { esyslog("svdrpservice: did not receive greeting from %s. Closing...", serverIp); Abort(); return false; } // do we need to convert between different charsets? DELETENULL(convIn); DELETENULL(convOut); cString convMsg = ""; if (greeting.First() && greeting.First()->Text()) { const char *l; const char *r = strrchr(greeting.First()->Text(), ';'); // at least two semicolons found if (r != strchr(greeting.First()->Text(), ';')) { r = skipspace(++r); l = cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"; if (charsetcmp(r, l) != 0) { convIn = new cCharSetConv(r, l); convOut = new cCharSetConv(l, r); convMsg = cString::sprintf(" (enabled charset conversion %s - %s)", r, l); } } } isyslog("SvdrpService: connected to %s:%hu%s", serverIp, serverPort, *convMsg); } return true; } void cSvdrpConnection::Close(void) { if (Send("QUIT\r\n", false)) Receive(); file.Close(); } void cSvdrpConnection::Abort(void) { file.Close(); } bool cSvdrpConnection::Send(const char *Cmd, bool Reconnect) { if (!Cmd) return false; if (Reconnect && !file.IsOpen()) Open(); if (!file.IsOpen()) { esyslog("svdrpservice: unable to send command to %s. Socket is closed", serverIp); return false; } DEBUG_PRINTF("SEND %s", Cmd); if (convOut) Cmd = convOut->Convert(Cmd); unsigned int len = ::strlen(Cmd); if (safe_write(file, Cmd, len) < 0) { esyslog("svdrpservice: error while writing to %s: %m", serverIp); Abort(); return false; } return true; } unsigned short cSvdrpConnection::Receive(cList* List, bool Connecting) { int timeoutMs = (Connecting ? SvdrpServiceSetup.connectTimeout : SvdrpServiceSetup.readTimeout) * 1000; while (ReadLine(timeoutMs)) { char *tail; long int code = ::strtol(buffer, &tail, 10); if (tail - buffer == 3 && code >= 100 && code <= 999 && (*tail == ' ' || *tail =='-')) { if (List) { const char* s = buffer + 4; if (convIn) s = convIn->Convert(s); List->Add(new cLine(s)); } if (*tail == ' ') return (unsigned short) code; } else { esyslog("svdrpservice: invalid reply from %s: '%s'", serverIp, buffer); Close(); break; } } if (List) List->Clear(); return 0; } bool cSvdrpConnection::ReadLine(int TimeoutMs) { if (!file.IsOpen()) return false; unsigned int tail = 0; while (cFile::FileReady(file, TimeoutMs)) { unsigned char c; int r = safe_read(file, &c, 1); if (r > 0) { if (c == '\n' || c == 0x00) { // line complete, make sure the string is terminated buffer[tail] = 0; DEBUG_PRINTF("READ %s\n", buffer); return true; } else if ((c <= 0x1F || c == 0x7F) && c != 0x09) { // ignore control characters } else { if (tail >= bufSize - 1) { bufSize += BUFSIZ; buffer = (char*) realloc(buffer, bufSize); if (!buffer) { esyslog("svdrpservice: unable to increase buffer size to %d byte", bufSize); Close(); return false; } } buffer[tail++] = c; } } else { esyslog("svdrpservice: lost connection to %s", serverIp); buffer[0] = 0; Abort(); return false; } } esyslog("svdrpservice: timeout waiting for reply from %s", serverIp); buffer[0] = 0; Abort(); return false; }