/* upnpd project * * * upnpd device * * This file is part of upnpd. * * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "config.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "upnpdpath.h" #include "upnpsoap.h" #include "upnpevents.h" #include #include #include #include #include #include #include #include "upnpglobalvars.h" #include "getifaddr.h" #if 0 #define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much? #endif #define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much? #define MIN_BUFFER_SIZE 65536 struct upnphttp * New_upnphttp (int s) { struct upnphttp *ret; if (s < 0) return NULL; ret = (struct upnphttp *) malloc (sizeof (struct upnphttp)); if (ret == NULL) return NULL; memset (ret, 0, sizeof (struct upnphttp)); ret->socket = s; return ret; } void CloseSocket_upnphttp (struct upnphttp *h) { if (close (h->socket) < 0) { syslog (LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); } h->socket = -1; h->state = 100; } void Delete_upnphttp (struct upnphttp *h) { if (h) { if (h->socket >= 0) CloseSocket_upnphttp (h); if (h->req_buf) free (h->req_buf); if (h->res_buf) free (h->res_buf); free (h); } } static void ParseHttpHeaders (struct upnphttp *h) { char *line; char *colon; char *p; int n; line = h->req_buf; /* TODO : check if req_buf, contentoff are ok */ while (line < (h->req_buf + h->req_contentoff)) { colon = strchr (line, ':'); if (colon) { if (strncasecmp (line, "Content-Length", 14) == 0) { p = colon; while (*p < '0' || *p > '9') p++; h->req_contentlen = atoi (p); } else if (strncasecmp (line, "SOAPAction", 10) == 0) { p = colon; n = 0; while (*p == ':' || *p == ' ' || *p == '\t') p++; while (p[n] >= ' ') { n++; } if ((p[0] == '"' && p[n - 1] == '"') || (p[0] == '\'' && p[n - 1] == '\'')) { p++; n -= 2; } h->req_soapAction = p; h->req_soapActionLen = n; } else if (strncasecmp (line, "Callback", 8) == 0) { p = colon; while (*p != '<' && *p != '\r') p++; n = 0; while (p[n] != '>' && p[n] != '\r') n++; h->req_Callback = p + 1; h->req_CallbackLen = MAX (0, n - 1); } else if (strncasecmp (line, "SID", 3) == 0) { //Skip extra headers like "SIDHEADER: xxxxxx xxx" for (p = line + 3; p < colon; p++) { if (!isspace (*p)) { p = NULL; //unexpected header break; } } if (p) { p = colon + 1; while (isspace (*p)) p++; n = 0; while (!isspace (p[n])) n++; h->req_SID = p; h->req_SIDLen = n; } } /* Timeout: Seconds-nnnn */ /* TIMEOUT * Recommended. Requested duration until subscription expires, * either number of seconds or infinite. Recommendation * by a UPnP Forum working committee. Defined by UPnP vendor. * Consists of the keyword "Second-" followed (without an * intervening space) by either an integer or the keyword "infinite". */ else if (strncasecmp (line, "Timeout", 7) == 0) { p = colon + 1; while (isspace (*p)) p++; if (strncasecmp (p, "Second-", 7) == 0) { h->req_Timeout = atoi (p + 7); } } // Range: bytes=xxx-yyy else if (strncasecmp (line, "Range", 5) == 0) { p = colon + 1; while (isspace (*p)) p++; if (strncasecmp (p, "bytes=", 6) == 0) { h->reqflags |= FLAG_RANGE; h->req_RangeEnd = atoll (strchr (p + 6, '-') + 1); h->req_RangeStart = atoll (p + 6); } } } while (!(line[0] == '\r' && line[1] == '\n')) line++; line += 2; } } void Send400 (struct upnphttp *h) { static const char body400[] = "400 Bad Request" "

Bad Request

The request is invalid" " for this HTTP version.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 400, "Bad Request", body400, sizeof (body400) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } /* very minimalistic 404 error message */ void Send404 (struct upnphttp *h) { static const char body404[] = "404 Not Found" "

Not Found

The requested URL was not found" " on this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 404, "Not Found", body404, sizeof (body404) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } /* very minimalistic 406 error message */ void Send406 (struct upnphttp *h) { static const char body406[] = "406 Not Acceptable" "

Not Acceptable

An unsupported operation" " was requested.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 406, "Not Acceptable", body406, sizeof (body406) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } /* very minimalistic 416 error message */ void Send416 (struct upnphttp *h) { static const char body416[] = "416 Requested Range Not Satisfiable" "

Requested Range Not Satisfiable

The requested range" " was outside the file's size.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 416, "Requested Range Not Satisfiable", body416, sizeof (body416) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } /* very minimalistic 500 error message */ void Send500 (struct upnphttp *h) { static const char body500[] = "500 Internal Server Error" "

Internal Server Error

Server encountered " "and Internal Error.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 500, "Internal Server Errror", body500, sizeof (body500) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } /* very minimalistic 501 error message */ void Send501 (struct upnphttp *h) { static const char body501[] = "501 Not Implemented" "

Not Implemented

The HTTP Method " "is not implemented by this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 501, "Not Implemented", body501, sizeof (body501) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } const char * findendheaders (const char *s, int len) { while (len-- > 0) { if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n') return s; s++; } return NULL; } /* Sends the description generated by the parameter */ void sendXMLdesc (struct upnphttp *h, char *(f) (int *)) { char *desc; int len; desc = f (&len); if (!desc) { static const char error500[] = "Error 500" "Internal Server Error\r\n"; syslog (LOG_ERR, "Failed to generate XML description"); h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 500, "Internal Server Error", error500, sizeof (error500) - 1); } else { BuildResp_upnphttp (h, desc, len); } SendResp_upnphttp (h); CloseSocket_upnphttp (h); free (desc); } /* ProcessHTTPPOST_upnphttp() * executes the SOAP query if it is possible */ void ProcessHTTPPOST_upnphttp (struct upnphttp *h) { if ((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { if (h->req_soapAction) { /* we can process the request */ syslog (LOG_INFO, "SOAPAction: %.*s", h->req_soapActionLen, h->req_soapAction); ExecuteSoapAction (h, h->req_soapAction, h->req_soapActionLen); } else { static const char err400str[] = "Bad request"; syslog (LOG_INFO, "No SOAPAction in HTTP headers"); h->respflags = FLAG_HTML; BuildResp2_upnphttp (h, 400, "Bad Request", err400str, sizeof (err400str) - 1); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } } else { /* waiting for remaining data */ h->state = 1; } } void ProcessHTTPSubscribe_upnphttp (struct upnphttp *h, const char *path) { const char *sid; syslog (LOG_DEBUG, "ProcessHTTPSubscribe %s", path); syslog (LOG_DEBUG, "Callback '%.*s' Timeout=%d", h->req_CallbackLen, h->req_Callback, h->req_Timeout); syslog (LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); if (!h->req_Callback && !h->req_SID) { /* Missing or invalid CALLBACK : 412 Precondition Failed. * If CALLBACK header is missing or does not contain a valid HTTP URL, * the publisher must respond with HTTP error 412 Precondition Failed*/ BuildResp2_upnphttp (h, 412, "Precondition Failed", 0, 0); SendResp_upnphttp (h); CloseSocket_upnphttp (h); } else { /* - add to the subscriber list * - respond HTTP/x.x 200 OK * - Send the initial event message */ /* Server:, SID:; Timeout: Second-(xx|infinite) */ if (h->req_Callback) { sid = upnpevents_addSubscriber (path, h->req_Callback, h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if (sid) { syslog (LOG_DEBUG, "generated sid=%s", sid); h->respflags |= FLAG_SID; h->req_SID = sid; h->req_SIDLen = strlen (sid); } BuildResp_upnphttp (h, 0, 0); } else { /* subscription renew */ /* Invalid SID * 412 Precondition Failed. If a SID does not correspond to a known, * un-expired subscription, the publisher must respond * with HTTP error 412 Precondition Failed. */ if (renewSubscription (h->req_SID, h->req_Timeout) < 0) { BuildResp2_upnphttp (h, 412, "Precondition Failed", 0, 0); } else { BuildResp_upnphttp (h, 0, 0); } } SendResp_upnphttp (h); CloseSocket_upnphttp (h); } } void ProcessHTTPUnSubscribe_upnphttp (struct upnphttp *h, const char *path) { syslog (LOG_DEBUG, "ProcessHTTPUnSubscribe %s", path); syslog (LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); /* Remove from the list */ if (upnpevents_removeSubscriber (h->req_SID) < 0) { BuildResp2_upnphttp (h, 412, "Precondition Failed", 0, 0); } else { BuildResp_upnphttp (h, 0, 0); } SendResp_upnphttp (h); CloseSocket_upnphttp (h); } /* Parse and process Http Query * called once all the HTTP headers have been received. */ void ProcessHttpQuery_upnphttp (struct upnphttp *h) { char HttpCommand[16]; char HttpUrl[512]; char *HttpVer; char *p; int i; p = h->req_buf; if (!p) return; for (i = 0; i < 15 && *p != ' ' && *p != '\r'; i++) HttpCommand[i] = *(p++); HttpCommand[i] = '\0'; while (*p == ' ') p++; if (strncmp (p, "http://", 7) == 0) { p = p + 7; while (*p != '/') p++; } for (i = 0; i < 511 && *p != ' ' && *p != '\r'; i++) HttpUrl[i] = *(p++); HttpUrl[i] = '\0'; while (*p == ' ') p++; HttpVer = h->HttpVer; for (i = 0; i < 15 && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; syslog (LOG_INFO, "HTTP REQUEST : %s %s (%s)", HttpCommand, HttpUrl, HttpVer); ParseHttpHeaders (h); /* see if we need to wait for remaining data */ if ((h->reqflags & FLAG_CHUNKED)) { if (h->req_chunklen) { h->state = 2; return; } char *chunkstart, *chunk, *endptr, *endbuf; chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff; while ((h->req_chunklen = strtol (chunk, &endptr, 16)) && (endptr != chunk)) { while (!(endptr[0] == '\r' && endptr[1] == '\n')) { endptr++; } endptr += 2; memmove (endbuf, endptr, h->req_chunklen); endbuf += h->req_chunklen; chunk = endptr + h->req_chunklen; } h->req_contentlen = endbuf - chunkstart; h->req_buflen = endbuf - h->req_buf; h->state = 100; } syslog (LOG_WARNING, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); if (strcmp ("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp (h); } else if ((strcmp ("GET", HttpCommand) == 0) || (strcmp ("HEAD", HttpCommand) == 0)) { h->req_command = EGet; if (strcmp (ROOTDESC_PATH, HttpUrl) == 0) { sendXMLdesc (h, genRootDesc); } else if (strcmp (BASICMANAGEMENT_PATH, HttpUrl) == 0) { sendXMLdesc (h, genBasicManagement); } else if (strcmp (CONFIGURATIONMANAGEMENT_PATH, HttpUrl) == 0) { sendXMLdesc (h, genConfigurationManagement); } else if (strcmp (SOFTWAREMANAGEMENT_PATH, HttpUrl) == 0) { sendXMLdesc (h, genSoftwareManagement); } else if (strcmp (LOWPOWERDEVICE_PATH, HttpUrl) == 0) { sendXMLdesc (h, genLowPowerDevice); } else { syslog (LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); Send404 (h); } } else if (strcmp ("SUBSCRIBE", HttpCommand) == 0) { h->req_command = ESubscribe; ProcessHTTPSubscribe_upnphttp (h, HttpUrl); } else if (strcmp ("UNSUBSCRIBE", HttpCommand) == 0) { h->req_command = EUnSubscribe; ProcessHTTPUnSubscribe_upnphttp (h, HttpUrl); } else { syslog (LOG_NOTICE, "Unsupported HTTP Command %s", HttpCommand); Send501 (h); } } void Process_upnphttp (struct upnphttp *h) { char buf[2048]; int n; if (!h) return; switch (h->state) { case 0: n = recv (h->socket, buf, 2048, 0); if (n < 0) { syslog (LOG_ERR, "recv (state0): %m"); h->state = 100; } else if (n == 0) { syslog (LOG_WARNING, "HTTP Connection closed unexpectedly"); h->state = 100; } else { const char *endheaders; /* if 1st arg of realloc() is null, * realloc behaves the same as malloc() */ h->req_buf = (char *) realloc (h->req_buf, n + h->req_buflen + 1); memcpy (h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; h->req_buf[h->req_buflen] = '\0'; /* search for the string "\r\n\r\n" */ endheaders = findendheaders (h->req_buf, h->req_buflen); if (endheaders) { h->req_contentoff = endheaders - h->req_buf + 4; h->req_contentlen = h->req_buflen - h->req_contentoff; ProcessHttpQuery_upnphttp (h); } } break; case 1: case 2: n = recv (h->socket, buf, 2048, 0); if (n < 0) { syslog (LOG_WARNING, "recv (state%d): %s\n", h->state, strerror (errno)); h->state = 100; } else if (n == 0) { syslog (LOG_WARNING, "HTTP Connection closed unexpectedly\n"); h->state = 100; } else { h->req_buf = (char *) realloc (h->req_buf, n + h->req_buflen); memcpy (h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; if ((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { /* Need the struct to point to the realloc'd memory locations */ if (h->state == 1) { ParseHttpHeaders (h); ProcessHTTPPOST_upnphttp (h); } else if (h->state == 2) { ProcessHttpQuery_upnphttp (h); } } } break; default: syslog (LOG_WARNING, "Unexpected state: %d", h->state); } } static const char httpresphead[] = "%s %d %s\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "Content-Length: %d\r\n" "Server: " UPNPD_SERVER_STRING "\r\n"; /* with response code and response message * also allocate enough memory */ void BuildHeader_upnphttp (struct upnphttp *h, int respcode, const char *respmsg, int bodylen) { int templen; if (!h->res_buf) { templen = sizeof (httpresphead) + 192 + bodylen; h->res_buf = (char *) malloc (templen); h->res_buf_alloclen = templen; } h->res_buflen = snprintf (h->res_buf, h->res_buf_alloclen, httpresphead, "HTTP/1.1", respcode, respmsg, (h-> respflags & FLAG_HTML) ? "text/html" : "text/xml", bodylen); /* Additional headers */ if (h->respflags & FLAG_TIMEOUT) { h->res_buflen += snprintf (h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Timeout: Second-"); if (h->req_Timeout) { h->res_buflen += snprintf (h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "%d\r\n", h->req_Timeout); } else { h->res_buflen += snprintf (h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "infinite\r\n"); } } if (h->respflags & FLAG_SID) { h->res_buflen += snprintf (h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "SID: %s\r\n", h->req_SID); } h->res_buf[h->res_buflen++] = '\r'; h->res_buf[h->res_buflen++] = '\n'; if (h->res_buf_alloclen < (h->res_buflen + bodylen)) { h->res_buf = (char *) realloc (h->res_buf, (h->res_buflen + bodylen)); h->res_buf_alloclen = h->res_buflen + bodylen; } } void BuildResp2_upnphttp (struct upnphttp *h, int respcode, const char *respmsg, const char *body, int bodylen) { BuildHeader_upnphttp (h, respcode, respmsg, bodylen); if (body) memcpy (h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; } /* responding 200 OK ! */ void BuildResp_upnphttp (struct upnphttp *h, const char *body, int bodylen) { BuildResp2_upnphttp (h, 200, "OK", body, bodylen); } void SendResp_upnphttp (struct upnphttp *h) { int n; n = send (h->socket, h->res_buf, h->res_buflen, 0); if (n < 0) { syslog (LOG_ERR, "send(res_buf): %m"); } else if (n < h->res_buflen) { /* TODO : handle correctly this case */ syslog (LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", n, h->res_buflen); } }