/** * ======================== legal notice ====================== * * File: AbstractSocket.cc * Created: 4. Juli 2012, 07 * Author: Geronimo * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling * * CMP - compound media player * * is a client/server mediaplayer intended to play any media from any workstation * without the need to export or mount shares. cmps is an easy to use backend * with a (ready to use) HTML-interface. Additionally the backend supports * authentication via HTTP-digest authorization. * cmpc is a client with vdr-like osd-menues. * * Copyright (c) 2012 Reinhard Mantey, some rights reserved! * published under Creative Commons by-sa * For details see http://creativecommons.org/licenses/by-sa/3.0/ * * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp * * -------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include cAbstractSocket::cAbstractSocket(int Port, int Queue) : sock(-1) , queue(Queue) , blocking(true) , thisSide(NULL) { } cAbstractSocket::cAbstractSocket(const char *ServerName, int Port) : sock(-1) , queue(0) , blocking(true) , thisSide(NULL) { others.push_back(new cConnectionPoint(ServerName, Port)); } cAbstractSocket::~cAbstractSocket() { Close(); if (thisSide) delete thisSide; cConnectionPoint *p; for (size_t i=0; i < others.size(); ++i) { p = others[i]; delete p; } } void cAbstractSocket::Close(void) { if (sock >= 0) { close(sock); sock = -1; } } // the client side bool cAbstractSocket::Connect(void) { if (sock < 0) { struct addrinfo hints, *result, *rp; char buf[20]; int s; /* Obtain address(es) matching host/port */ memset(buf, 0, sizeof(buf)); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; snprintf(buf, sizeof(buf), "%d", OtherSide()->Port()); if ((s = getaddrinfo(OtherSide()->HostName(), buf, &hints, &result))) { esyslog("getaddrinfo: %s", gai_strerror(s)); return -1; } // getaddrinfo potentially returns a list of addresses, that may fit, // so try each address until we successfully connect for (rp = result; rp; rp = rp->ai_next) { sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock == -1) continue; if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) break; // we got a connection close(sock); } if (rp == NULL) { // no address could be connected to esyslog("Could not connect!"); Close(); return false; } // here we have a valid connection, so lets gather some information thisSide = GetNameAndAddress(sock, rp->ai_addr, rp->ai_addrlen); OtherSide()->SetSocket(sock); char nameBuf[512]; memset(nameBuf, 0, sizeof(nameBuf)); if ((s = getnameinfo(rp->ai_addr, rp->ai_addrlen, nameBuf, sizeof(nameBuf), NULL, 0, NI_NAMEREQD))) { esyslog("failed to determine servers hostname %s", gai_strerror(s)); } else { OtherSide()->SetRealName(nameBuf); } freeaddrinfo(result); } return sock > 0; } // as each address may be IP4 or IP6, cut this off static void * getAddr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) return &(((struct sockaddr_in*)sa)->sin_addr); return &(((struct sockaddr_in6*)sa)->sin6_addr); } // same is true for port access static int getPort(struct sockaddr *sa) { if (sa->sa_family == AF_INET) return ((struct sockaddr_in*)sa)->sin_port; return ((struct sockaddr_in6*)sa)->sin6_port; } cConnectionPoint *cAbstractSocket::GetNameAndAddress(int Socket, struct sockaddr *sa, socklen_t sa_size) { cConnectionPoint *rv = NULL; char nameBuf[512], addrBuf[INET6_ADDRSTRLEN]; if (getnameinfo(sa, sa_size, nameBuf, sizeof(nameBuf), NULL, 0, NI_NAMEREQD)) { // ok, just ip address has to suffice to start work rv = new cConnectionPoint(inet_ntop(sa->sa_family, getAddr(sa), addrBuf, sizeof(addrBuf)) , ntohs(getPort(sa)) , inet_ntop(sa->sa_family, getAddr(sa), addrBuf, sizeof(addrBuf))); } else { rv = new cConnectionPoint(inet_ntop(sa->sa_family, getAddr(sa), addrBuf, sizeof(addrBuf)) , ntohs(getPort(sa)) , nameBuf); } if (rv) rv->SetSocket(Socket); return rv; } void cAbstractSocket::SetBlockingIO(bool ForceBlockingIO) { blocking = ForceBlockingIO; } // server side first part bool cAbstractSocket::Open(int Port) { if (sock < 0) { isyslog("socket is < 0 - so start a new connection ..."); struct addrinfo hints, *result, *rp; char buf[20]; int s; memset(&hints, 0, sizeof(hints)); memset(buf, 0, sizeof(buf)); hints.ai_family = AF_UNSPEC; // allow IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; snprintf(buf, sizeof(buf), "%d", Port); if ((s = getaddrinfo(NULL, buf, &hints, &result))) { esyslog("getaddrinfo: %s\n", gai_strerror(s)); return false; } int yes=1; // getaddrinfo potentially returns a list of addresses, that may fit, // so try each address until we successfully bound for (rp = result; rp; rp = rp->ai_next) { sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock == -1) continue; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); if (bind(sock, rp->ai_addr, rp->ai_addrlen) != -1) break; // ok, we succeeded to bind close(sock); } if (!rp) { esyslog("could not bind"); Close(); return false; } ConfigureSocket(sock); freeaddrinfo(result); // listen to the socket: if (listen(sock, queue) < 0) { esyslog("failed to listen to bound socket!"); Close(); return (false); } } return (true); } // server side second part cConnectionPoint *cAbstractSocket::Accept(int Port, int TimeoutMs) { cConnectionPoint *client = NULL; if (Open(Port)) { struct sockaddr_storage clientname; uint cl_size = sizeof(clientname); struct pollfd pf[1]; pf[0].fd = sock; pf[0].events = POLLIN; if (poll(pf, 1, TimeoutMs) < 1) return NULL; int newsock = accept(sock, (struct sockaddr *) &clientname, &cl_size); if (newsock > 0) { isyslog("ok, got client connection request ..."); // if server address has not been restricted at bind-time, its possible that we don't // have a local address yet. If server is bound to 0 (any address), we can determine // the real server address in use now. But only do it once. if (!thisSide) { struct sockaddr_storage server; uint sa_size = sizeof(server); if (getpeername(newsock, (struct sockaddr *) &server, &sa_size)) { esyslog("getpeername failed: #%d", errno); } else thisSide = GetNameAndAddress(newsock, (struct sockaddr *) &server, sa_size); } // At server side, multiple clients can have open connections at the same time, // so get rid of that here. We always fetch connections informations // before any access decisions, so those could be extended to use names // some day ... client = GetNameAndAddress(newsock, (struct sockaddr *) &clientname, cl_size); if (client) { bool accepted = true; //FIXME: change determination of accepting client access! // accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr); // change to: // accepted = SVDRPHosts.Acceptable(cpi); // so in SVDRPHosts you have IP-Address and real name (if accessible) if (!accepted) { const char *s = "Access denied!\n"; if (write(newsock, s, strlen(s)) < 0) { esyslog(s); close(newsock); } newsock = -1; client = NULL; } else { others.push_back(client); } isyslog("connect from %s, port %hu - %s", client->HostName(), client->Port(), accepted ? "accepted" : "DENIED"); } } else if (errno != EINTR && errno != EAGAIN) { esyslog("failed to accept client"); } } return (client); }