1 /* $Id: upnphttp.c,v 1.105 2016/02/16 12:15:02 nanard Exp $ */
2 /* vim: tabstop=4 shiftwidth=4 noexpandtab */
4 * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
5 * Author : Thomas Bernard
6 * Copyright (c) 2005-2015 Thomas Bernard
7 * This software is subject to the conditions detailed in the
8 * LICENCE file included in this distribution.
14 #include <sys/types.h>
15 #include <sys/socket.h>
16 #include <sys/param.h>
17 #include <netinet/in.h>
18 #include <arpa/inet.h>
23 #ifdef ENABLE_HTTP_DATE
27 #include "upnpdescgen.h"
28 #include "miniupnpdpath.h"
30 #include "upnpevents.h"
31 #include "upnputils.h"
34 #include <openssl/err.h>
35 #include <openssl/engine.h>
36 #include <openssl/conf.h>
37 static SSL_CTX
*ssl_ctx
= NULL
;
39 #ifndef HTTPS_CERTFILE
40 #define HTTPS_CERTFILE "/etc/ssl/certs/ssl-cert-snakeoil.pem"
43 #define HTTPS_KEYFILE "/etc/ssl/private/ssl-cert-snakeoil.key"
51 while((err
= ERR_get_error()) != 0) {
52 syslog(LOG_ERR
, "%s", ERR_error_string(err
, buffer
));
56 static int verify_callback(int preverify_ok
, X509_STORE_CTX
*ctx
)
58 syslog(LOG_DEBUG
, "verify_callback(%d, %p)", preverify_ok
, ctx
);
64 const SSL_METHOD
*method
;
66 SSL_load_error_strings();
67 method
= TLSv1_server_method();
69 syslog(LOG_ERR
, "TLSv1_server_method() failed");
73 ssl_ctx
= SSL_CTX_new(method
);
75 syslog(LOG_ERR
, "SSL_CTX_new() failed");
79 /* set the local certificate */
80 if(!SSL_CTX_use_certificate_file(ssl_ctx
, HTTPS_CERTFILE
, SSL_FILETYPE_PEM
)) {
81 syslog(LOG_ERR
, "SSL_CTX_use_certificate_file(%s) failed", HTTPS_CERTFILE
);
85 /* set the private key */
86 if(!SSL_CTX_use_PrivateKey_file(ssl_ctx
, HTTPS_KEYFILE
, SSL_FILETYPE_PEM
)) {
87 syslog(LOG_ERR
, "SSL_CTX_use_PrivateKey_file(%s) failed", HTTPS_KEYFILE
);
91 /* verify private key */
92 if(!SSL_CTX_check_private_key(ssl_ctx
)) {
93 syslog(LOG_ERR
, "SSL_CTX_check_private_key() failed");
97 /*SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, verify_callback);*/
98 SSL_CTX_set_verify(ssl_ctx
, SSL_VERIFY_NONE
, verify_callback
);
99 /*SSL_CTX_set_verify_depth(depth);*/
100 syslog(LOG_INFO
, "using %s", SSLeay_version(SSLEAY_VERSION
));
107 if(ssl_ctx
!= NULL
) {
108 SSL_CTX_free(ssl_ctx
);
113 CONF_modules_unload(1);
116 CRYPTO_cleanup_all_ex_data();
118 #endif /* ENABLE_HTTPS */
123 struct upnphttp
* ret
;
126 ret
= (struct upnphttp
*)malloc(sizeof(struct upnphttp
));
129 memset(ret
, 0, sizeof(struct upnphttp
));
131 if(!set_non_blocking(s
))
132 syslog(LOG_WARNING
, "New_upnphttp::set_non_blocking(): %m");
138 InitSSL_upnphttp(struct upnphttp
* h
)
141 h
->ssl
= SSL_new(ssl_ctx
);
143 syslog(LOG_ERR
, "SSL_new() failed");
147 if(!SSL_set_fd(h
->ssl
, h
->socket
)) {
148 syslog(LOG_ERR
, "SSL_set_fd() failed");
152 r
= SSL_accept(h
->ssl
); /* start the handshaking */
155 err
= SSL_get_error(h
->ssl
, r
);
156 syslog(LOG_DEBUG
, "SSL_accept() returned %d, SSL_get_error() %d", r
, err
);
157 if(err
!= SSL_ERROR_WANT_READ
&& err
!= SSL_ERROR_WANT_WRITE
) {
158 syslog(LOG_ERR
, "SSL_accept() failed");
164 #endif /* ENABLE_HTTPS */
167 CloseSocket_upnphttp(struct upnphttp
* h
)
169 /* SSL_shutdown() ? */
170 if(close(h
->socket
) < 0)
172 syslog(LOG_ERR
, "CloseSocket_upnphttp: close(%d): %m", h
->socket
);
175 h
->state
= EToDelete
;
179 Delete_upnphttp(struct upnphttp
* h
)
188 CloseSocket_upnphttp(h
);
197 /* parse HttpHeaders of the REQUEST
198 * This function is called after the \r\n\r\n character
199 * sequence has been found in h->req_buf */
201 ParseHttpHeaders(struct upnphttp
* h
)
207 if((h
->req_buf
== NULL
) || (h
->req_contentoff
<= 0))
210 while(line
< (h
->req_buf
+ h
->req_contentoff
))
215 if(*colon
== '\r' || *colon
== '\n')
217 colon
= NULL
; /* no ':' character found on the line */
224 if(strncasecmp(line
, "Content-Length:", 15)==0)
227 while((*p
< '0' || *p
> '9') && (*p
!= '\r') && (*p
!= '\n'))
229 h
->req_contentlen
= atoi(p
);
230 if(h
->req_contentlen
< 0) {
231 syslog(LOG_WARNING
, "ParseHttpHeaders() invalid Content-Length %d", h
->req_contentlen
);
232 h
->req_contentlen
= 0;
234 /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen);
235 printf(" readbufflen=%d contentoff = %d\n",
236 h->req_buflen, h->req_contentoff);*/
238 else if(strncasecmp(line
, "Host:", 5)==0)
242 while(*p
== ':' || *p
== ' ' || *p
== '\t')
246 h
->req_HostOff
= p
- h
->req_buf
;
249 else if(strncasecmp(line
, "SOAPAction:", 11)==0)
253 while(*p
== ':' || *p
== ' ' || *p
== '\t')
257 if((p
[0] == '"' && p
[n
-1] == '"')
258 || (p
[0] == '\'' && p
[n
-1] == '\''))
262 h
->req_soapActionOff
= p
- h
->req_buf
;
263 h
->req_soapActionLen
= n
;
265 else if(strncasecmp(line
, "accept-language:", 16) == 0)
269 while(*p
== ':' || *p
== ' ' || *p
== '\t')
273 syslog(LOG_DEBUG
, "accept-language HTTP header : '%.*s'", n
, p
);
274 /* keep only the 1st accepted language */
276 while(p
[n
]>' ' && p
[n
] != ',')
278 if(n
>= (int)sizeof(h
->accept_language
))
279 n
= (int)sizeof(h
->accept_language
) - 1;
280 memcpy(h
->accept_language
, p
, n
);
281 h
->accept_language
[n
] = '\0';
283 else if(strncasecmp(line
, "expect:", 7) == 0)
287 while(*p
== ':' || *p
== ' ' || *p
== '\t')
291 if(strncasecmp(p
, "100-continue", 12) == 0) {
292 h
->respflags
|= FLAG_CONTINUE
;
293 syslog(LOG_DEBUG
, "\"Expect: 100-Continue\" header detected");
297 else if(strncasecmp(line
, "Callback:", 9)==0)
299 /* The Callback can contain several urls :
300 * If there is more than one URL, when the service sends
301 * events, it will try these URLs in order until one
302 * succeeds. One or more URLs each enclosed by angle
303 * brackets ("<" and ">") */
305 while(*p
!= '<' && *p
!= '\r' )
310 while(n
> 0 && p
[n
] != '>')
312 /* found last > character */
313 h
->req_CallbackOff
= p
- h
->req_buf
;
314 h
->req_CallbackLen
= MAX(0, n
+ 1);
316 else if(strncasecmp(line
, "SID:", 4)==0)
319 while((*p
== ' ') || (*p
== '\t'))
322 while(!isspace(p
[n
]))
324 h
->req_SIDOff
= p
- h
->req_buf
;
327 /* Timeout: Seconds-nnnn */
329 Recommended. Requested duration until subscription expires,
330 either number of seconds or infinite. Recommendation
331 by a UPnP Forum working committee. Defined by UPnP vendor.
332 Consists of the keyword "Second-" followed (without an
333 intervening space) by either an integer or the keyword "infinite". */
334 else if(strncasecmp(line
, "Timeout:", 8)==0)
337 while((*p
== ' ') || (*p
== '\t'))
339 if(strncasecmp(p
, "Second-", 7)==0) {
340 h
->req_Timeout
= atoi(p
+7);
344 else if(strncasecmp(line
, "nt:", 3)==0)
347 while((*p
== ' ') || (*p
== '\t'))
350 while(!isspace(p
[n
]))
352 h
->req_NTOff
= p
- h
->req_buf
;
355 #endif /* UPNP_STRICT */
356 #endif /* ENABLE_EVENTS */
358 /* the loop below won't run off the end of the buffer
359 * because the buffer is guaranteed to contain the \r\n\r\n
360 * character sequence */
361 while(!(line
[0] == '\r' && line
[1] == '\n'))
367 /* very minimalistic 404 error message */
369 Send404(struct upnphttp
* h
)
371 static const char body404
[] =
372 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
373 "<BODY><H1>Not Found</H1>The requested URL was not found"
374 " on this server.</BODY></HTML>\r\n";
376 h
->respflags
= FLAG_HTML
;
377 BuildResp2_upnphttp(h
, 404, "Not Found",
378 body404
, sizeof(body404
) - 1);
379 SendRespAndClose_upnphttp(h
);
383 Send405(struct upnphttp
* h
)
385 static const char body405
[] =
386 "<HTML><HEAD><TITLE>405 Method Not Allowed</TITLE></HEAD>"
387 "<BODY><H1>Method Not Allowed</H1>The HTTP Method "
388 "is not allowed on this resource.</BODY></HTML>\r\n";
390 h
->respflags
|= FLAG_HTML
;
391 BuildResp2_upnphttp(h
, 405, "Method Not Allowed",
392 body405
, sizeof(body405
) - 1);
393 SendRespAndClose_upnphttp(h
);
396 /* very minimalistic 501 error message */
398 Send501(struct upnphttp
* h
)
400 static const char body501
[] =
401 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
402 "<BODY><H1>Not Implemented</H1>The HTTP Method "
403 "is not implemented by this server.</BODY></HTML>\r\n";
405 h
->respflags
= FLAG_HTML
;
406 BuildResp2_upnphttp(h
, 501, "Not Implemented",
407 body501
, sizeof(body501
) - 1);
408 SendRespAndClose_upnphttp(h
);
411 /* findendheaders() find the \r\n\r\n character sequence and
412 * return a pointer to it.
413 * It returns NULL if not found */
415 findendheaders(const char * s
, int len
)
419 if(s
[0]=='\r' && s
[1]=='\n' && s
[2]=='\r' && s
[3]=='\n')
426 #ifdef HAS_DUMMY_SERVICE
428 sendDummyDesc(struct upnphttp
* h
)
430 static const char xml_desc
[] = "<?xml version=\"1.0\"?>\r\n"
431 "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"
437 " <serviceStateTable />"
439 BuildResp_upnphttp(h
, xml_desc
, sizeof(xml_desc
)-1);
440 SendRespAndClose_upnphttp(h
);
444 /* Sends the description generated by the parameter */
446 sendXMLdesc(struct upnphttp
* h
, char * (f
)(int *))
453 static const char error500
[] = "<HTML><HEAD><TITLE>Error 500</TITLE>"
454 "</HEAD><BODY>Internal Server Error</BODY></HTML>\r\n";
455 syslog(LOG_ERR
, "Failed to generate XML description");
456 h
->respflags
= FLAG_HTML
;
457 BuildResp2_upnphttp(h
, 500, "Internal Server Error",
458 error500
, sizeof(error500
)-1);
462 BuildResp_upnphttp(h
, desc
, len
);
464 SendRespAndClose_upnphttp(h
);
468 /* ProcessHTTPPOST_upnphttp()
469 * executes the SOAP query if it is possible */
471 ProcessHTTPPOST_upnphttp(struct upnphttp
* h
)
473 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
475 /* the request body is received */
476 if(h
->req_soapActionOff
> 0)
478 /* we can process the request */
479 syslog(LOG_INFO
, "SOAPAction: %.*s",
480 h
->req_soapActionLen
, h
->req_buf
+ h
->req_soapActionOff
);
482 h
->req_buf
+ h
->req_soapActionOff
,
483 h
->req_soapActionLen
);
487 static const char err400str
[] =
488 "<html><body>Bad request</body></html>";
489 syslog(LOG_INFO
, "No SOAPAction in HTTP headers");
490 h
->respflags
= FLAG_HTML
;
491 BuildResp2_upnphttp(h
, 400, "Bad Request",
492 err400str
, sizeof(err400str
) - 1);
493 SendRespAndClose_upnphttp(h
);
496 else if(h
->respflags
& FLAG_CONTINUE
)
498 /* Sending the 100 Continue response */
500 h
->res_buf
= malloc(256);
501 h
->res_buf_alloclen
= 256;
503 h
->res_buflen
= snprintf(h
->res_buf
, h
->res_buf_alloclen
,
504 "%s 100 Continue\r\n\r\n", h
->HttpVer
);
506 h
->state
= ESendingContinue
;
507 if(SendResp_upnphttp(h
))
508 h
->state
= EWaitingForHttpContent
;
512 /* waiting for remaining data */
513 h
->state
= EWaitingForHttpContent
;
520 * check that url is on originating IP
521 * extract first correct URL
522 * returns 0 if the callback header value is not valid
526 checkCallbackURL(struct upnphttp
* h
)
534 if(h
->req_CallbackOff
<= 0 || h
->req_CallbackLen
< 10)
536 if(memcmp(h
->req_buf
+ h
->req_CallbackOff
, "<http://", 8) != 0) {
537 p
= h
->req_buf
+ h
->req_CallbackOff
+ 1;
540 /* extract host from url to addrstr[] */
542 p
= h
->req_buf
+ h
->req_CallbackOff
+ 8;
546 while(*p
!= ']' && *p
!= '>' && i
< (sizeof(addrstr
)-1)
547 && p
< (h
->req_buf
+ h
->req_CallbackOff
+ h
->req_CallbackLen
))
548 addrstr
[i
++] = *(p
++);
551 while(*p
!= '/' && *p
!= ':' && *p
!= '>' && i
< (sizeof(addrstr
)-1)
552 && p
< (h
->req_buf
+ h
->req_CallbackOff
+ h
->req_CallbackLen
))
553 addrstr
[i
++] = *(p
++);
559 struct in6_addr addr
;
560 if(inet_pton(AF_INET6
, addrstr
, &addr
) <= 0)
563 || (0!=memcmp(&addr
, &(h
->clientaddr_v6
), sizeof(struct in6_addr
))))
570 if(inet_pton(AF_INET
, addrstr
, &addr
) <= 0)
574 if(!IN6_IS_ADDR_V4MAPPED(&(h
->clientaddr_v6
)))
576 if(0!=memcmp(&addr
, ((const char *)&(h
->clientaddr_v6
) + 12), 4))
579 if(0!=memcmp(&addr
, &(h
->clientaddr
), sizeof(struct in_addr
)))
583 if(0!=memcmp(&addr
, &(h
->clientaddr
), sizeof(struct in_addr
)))
587 /* select only the good callback url */
588 while(p
< h
->req_buf
+ h
->req_CallbackOff
+ h
->req_CallbackLen
&& *p
!= '>')
590 h
->req_CallbackOff
++; /* skip initial '<' */
591 h
->req_CallbackLen
= (int)(p
- h
->req_buf
- h
->req_CallbackOff
);
594 while(p
< h
->req_buf
+ h
->req_CallbackOff
+ h
->req_CallbackLen
&& *p
!= '>')
596 if(*p
!= '>') return 0;
597 while(p
< h
->req_buf
+ h
->req_CallbackOff
+ h
->req_CallbackLen
&& *p
!= '<')
599 if(*p
!= '<') return 0;
600 h
->req_CallbackLen
-= (int)(p
- h
->req_buf
- h
->req_CallbackOff
);
601 h
->req_CallbackOff
= (int)(p
- h
->req_buf
);
606 ProcessHTTPSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
609 syslog(LOG_DEBUG
, "ProcessHTTPSubscribe %s", path
);
610 syslog(LOG_DEBUG
, "Callback '%.*s' Timeout=%d",
611 h
->req_CallbackLen
, h
->req_buf
+ h
->req_CallbackOff
,
613 syslog(LOG_DEBUG
, "SID '%.*s'", h
->req_SIDLen
, h
->req_buf
+ h
->req_SIDOff
);
614 #if defined(UPNP_STRICT) && (UPNP_VERSION_MAJOR > 1) || (UPNP_VERSION_MINOR > 0)
615 /*if(h->req_Timeout < 1800) {*/
616 if(h
->req_Timeout
== 0) {
617 /* Second-infinite is forbidden with UDA v1.1 and later :
618 * (UDA 1.1 : 4.1.1 Subscription)
619 * UPnP 1.1 control points MUST NOT subscribe using keyword infinite,
620 * UPnP 1.1 devices MUST NOT set actual subscription durations to
621 * "infinite". The presence of infinite in a request MUST be silently
622 * ignored by a UPnP 1.1 device (the presence of infinite is handled
623 * by the device as if the TIMEOUT header field in a request was not
624 * present) . The keyword infinite MUST NOT be returned by a UPnP 1.1
626 h
->req_Timeout
= 1800; /* default to 30 minutes */
628 #endif /* UPNP_STRICT */
629 if((h
->req_CallbackOff
<= 0) && (h
->req_SIDOff
<= 0)) {
630 /* Missing or invalid CALLBACK : 412 Precondition Failed.
631 * If CALLBACK header is missing or does not contain a valid HTTP URL,
632 * the publisher must respond with HTTP error 412 Precondition Failed*/
633 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
634 SendRespAndClose_upnphttp(h
);
636 /* - add to the subscriber list
637 * - respond HTTP/x.x 200 OK
638 * - Send the initial event message */
639 /* Server:, SID:; Timeout: Second-(xx|infinite) */
640 /* Check that the callback URL is on the same IP as
641 * the request, and not on the internet, nor on ourself (DOS attack ?) */
642 if(h
->req_CallbackOff
> 0) {
644 /* SID: and Callback: are incompatible */
645 if(h
->req_SIDOff
> 0) {
646 syslog(LOG_WARNING
, "Both Callback: and SID: in SUBSCRIBE");
647 BuildResp2_upnphttp(h
, 400, "Incompatible header fields", 0, 0);
648 /* "NT: upnp:event" header is mandatory */
649 } else if(h
->req_NTOff
<= 0 || h
->req_NTLen
!= 10 ||
650 0 != memcmp("upnp:event", h
->req_buf
+ h
->req_NTOff
, 10)) {
651 syslog(LOG_WARNING
, "Invalid NT in SUBSCRIBE %.*s",
652 h
->req_NTLen
, h
->req_buf
+ h
->req_NTOff
);
653 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
656 if(checkCallbackURL(h
)) {
657 sid
= upnpevents_addSubscriber(path
, h
->req_buf
+ h
->req_CallbackOff
,
658 h
->req_CallbackLen
, h
->req_Timeout
);
659 h
->respflags
= FLAG_TIMEOUT
;
661 syslog(LOG_DEBUG
, "generated sid=%s", sid
);
662 h
->respflags
|= FLAG_SID
;
665 BuildResp_upnphttp(h
, 0, 0);
667 syslog(LOG_WARNING
, "Invalid Callback in SUBSCRIBE %.*s",
668 h
->req_CallbackLen
, h
->req_buf
+ h
->req_CallbackOff
);
669 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
672 /* subscription renew */
674 412 Precondition Failed. If a SID does not correspond to a known,
675 un-expired subscription, the publisher must respond
676 with HTTP error 412 Precondition Failed. */
678 /* SID: and NT: headers are incompatibles */
679 if(h
->req_NTOff
> 0) {
680 syslog(LOG_WARNING
, "Both NT: and SID: in SUBSCRIBE");
681 BuildResp2_upnphttp(h
, 400, "Incompatible header fields", 0, 0);
683 #endif /* UPNP_STRICT */
684 sid
= upnpevents_renewSubscription(h
->req_buf
+ h
->req_SIDOff
,
685 h
->req_SIDLen
, h
->req_Timeout
);
687 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
689 h
->respflags
= FLAG_TIMEOUT
| FLAG_SID
;
691 BuildResp_upnphttp(h
, 0, 0);
695 #endif /* UPNP_STRICT */
697 SendRespAndClose_upnphttp(h
);
702 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
704 syslog(LOG_DEBUG
, "ProcessHTTPUnSubscribe %s", path
);
705 syslog(LOG_DEBUG
, "SID '%.*s'", h
->req_SIDLen
, h
->req_buf
+ h
->req_SIDOff
);
706 /* Remove from the list */
708 if(h
->req_SIDOff
<= 0 || h
->req_SIDLen
== 0) {
709 /* SID: header missing or empty */
710 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
711 } else if(h
->req_CallbackOff
> 0 || h
->req_NTOff
> 0) {
712 /* no NT: or Callback: header must be present */
713 BuildResp2_upnphttp(h
, 400, "Incompatible header fields", 0, 0);
716 if(upnpevents_removeSubscriber(h
->req_buf
+ h
->req_SIDOff
, h
->req_SIDLen
) < 0) {
717 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
719 BuildResp_upnphttp(h
, 0, 0);
721 SendRespAndClose_upnphttp(h
);
725 /* Parse and process Http Query
726 * called once all the HTTP headers have been received,
727 * so it is guaranteed that h->req_buf contains the \r\n\r\n
728 * character sequence */
730 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
732 static const struct {
736 { ROOTDESC_PATH
, genRootDesc
},
737 { WANIPC_PATH
, genWANIPCn
},
738 { WANCFG_PATH
, genWANCfg
},
739 #ifdef HAS_DUMMY_SERVICE
742 #ifdef ENABLE_L3F_SERVICE
745 #ifdef ENABLE_6FC_SERVICE
746 { WANIP6FC_PATH
, gen6FC
},
748 #ifdef ENABLE_DP_SERVICE
753 char HttpCommand
[16];
762 /* note : checking (*p != '\r') is enough to avoid runing off the
763 * end of the buffer, because h->req_buf is guaranteed to contain
764 * the \r\n\r\n character sequence */
765 for(i
= 0; i
<15 && *p
!= ' ' && *p
!= '\r'; i
++)
766 HttpCommand
[i
] = *(p
++);
767 HttpCommand
[i
] = '\0';
770 for(i
= 0; i
<127 && *p
!= ' ' && *p
!= '\r'; i
++)
775 HttpVer
= h
->HttpVer
;
776 for(i
= 0; i
<15 && *p
!= '\r'; i
++)
779 syslog(LOG_INFO
, "HTTP REQUEST from %s : %s %s (%s)",
780 h
->clientaddr_str
, HttpCommand
, HttpUrl
, HttpVer
);
782 if(h
->req_HostOff
> 0 && h
->req_HostLen
> 0) {
783 syslog(LOG_DEBUG
, "Host: %.*s", h
->req_HostLen
, h
->req_buf
+ h
->req_HostOff
);
784 p
= h
->req_buf
+ h
->req_HostOff
;
788 while(p
< h
->req_buf
+ h
->req_HostOff
+ h
->req_HostLen
) {
790 /* TODO check *p in [0-9a-f:.] */
794 syslog(LOG_NOTICE
, "DNS rebinding attack suspected (Host: %.*s)", h
->req_HostLen
, h
->req_buf
+ h
->req_HostOff
);
799 /* TODO : Check port */
801 for(i
= 0; i
< h
->req_HostLen
; i
++) {
802 if(*p
!= ':' && *p
!= '.' && (*p
> '9' || *p
< '0')) {
803 syslog(LOG_NOTICE
, "DNS rebinding attack suspected (Host: %.*s)", h
->req_HostLen
, h
->req_buf
+ h
->req_HostOff
);
811 if(strcmp("POST", HttpCommand
) == 0)
813 h
->req_command
= EPost
;
814 ProcessHTTPPOST_upnphttp(h
);
816 else if(strcmp("GET", HttpCommand
) == 0)
818 h
->req_command
= EGet
;
819 for(i
=0; path_desc
[i
].path
; i
++) {
820 if(strcasecmp(path_desc
[i
].path
, HttpUrl
) == 0) {
822 sendXMLdesc(h
, path_desc
[i
].f
);
824 #ifdef HAS_DUMMY_SERVICE
832 if(0 == memcmp(HttpUrl
, "/ctl/", 5)) {
833 /* 405 Method Not Allowed
835 h
->respflags
= FLAG_ALLOW_POST
;
840 if(0 == memcmp(HttpUrl
, "/evt/", 5)) {
841 /* 405 Method Not Allowed
842 * Allow: SUBSCRIBE, UNSUBSCRIBE */
843 h
->respflags
= FLAG_ALLOW_SUB_UNSUB
;
848 syslog(LOG_NOTICE
, "%s not found, responding ERROR 404", HttpUrl
);
852 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
854 h
->req_command
= ESubscribe
;
855 ProcessHTTPSubscribe_upnphttp(h
, HttpUrl
);
857 else if(strcmp("UNSUBSCRIBE", HttpCommand
) == 0)
859 h
->req_command
= EUnSubscribe
;
860 ProcessHTTPUnSubscribe_upnphttp(h
, HttpUrl
);
863 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
865 syslog(LOG_NOTICE
, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled");
871 syslog(LOG_NOTICE
, "Unsupported HTTP Command %s", HttpCommand
);
878 Process_upnphttp(struct upnphttp
* h
)
888 case EWaitingForHttpRequest
:
891 n
= SSL_read(h
->ssl
, buf
, sizeof(buf
));
893 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
896 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
903 err
= SSL_get_error(h
->ssl
, n
);
904 if(err
!= SSL_ERROR_WANT_READ
&& err
!= SSL_ERROR_WANT_WRITE
)
906 syslog(LOG_ERR
, "SSL_read() failed");
908 h
->state
= EToDelete
;
912 if(errno
!= EAGAIN
&&
913 errno
!= EWOULDBLOCK
&&
916 syslog(LOG_ERR
, "recv (state0): %m");
917 h
->state
= EToDelete
;
919 /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */
926 syslog(LOG_WARNING
, "HTTP Connection from %s closed unexpectedly", inet_ntoa(h
->clientaddr
));
927 h
->state
= EToDelete
;
931 const char * endheaders
;
932 /* if 1st arg of realloc() is null,
933 * realloc behaves the same as malloc() */
934 h_tmp
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
+ 1);
937 syslog(LOG_WARNING
, "Unable to allocate new memory for h->req_buf)");
938 h
->state
= EToDelete
;
943 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
945 h
->req_buf
[h
->req_buflen
] = '\0';
947 /* search for the string "\r\n\r\n" */
948 endheaders
= findendheaders(h
->req_buf
, h
->req_buflen
);
951 /* at this point, the request buffer (h->req_buf)
952 * is guaranteed to contain the \r\n\r\n character sequence */
953 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
954 ProcessHttpQuery_upnphttp(h
);
958 case EWaitingForHttpContent
:
961 n
= SSL_read(h
->ssl
, buf
, sizeof(buf
));
963 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
966 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
973 err
= SSL_get_error(h
->ssl
, n
);
974 if(err
!= SSL_ERROR_WANT_READ
&& err
!= SSL_ERROR_WANT_WRITE
)
976 syslog(LOG_ERR
, "SSL_read() failed");
978 h
->state
= EToDelete
;
982 if(errno
!= EAGAIN
&&
983 errno
!= EWOULDBLOCK
&&
986 syslog(LOG_ERR
, "recv (state1): %m");
987 h
->state
= EToDelete
;
989 /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */
996 syslog(LOG_WARNING
, "HTTP Connection from %s closed unexpectedly", inet_ntoa(h
->clientaddr
));
997 h
->state
= EToDelete
;
1001 void * tmp
= realloc(h
->req_buf
, n
+ h
->req_buflen
);
1004 syslog(LOG_ERR
, "memory allocation error %m");
1005 h
->state
= EToDelete
;
1010 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
1012 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
1014 ProcessHTTPPOST_upnphttp(h
);
1019 case ESendingContinue
:
1020 if(SendResp_upnphttp(h
))
1021 h
->state
= EWaitingForHttpContent
;
1023 case ESendingAndClosing
:
1024 SendRespAndClose_upnphttp(h
);
1027 syslog(LOG_WARNING
, "Unexpected state: %d", h
->state
);
1031 static const char httpresphead
[] =
1033 "Content-Type: %s\r\n"
1034 "Connection: close\r\n"
1035 "Content-Length: %d\r\n"
1036 "Server: " MINIUPNPD_SERVER_STRING
"\r\n"
1040 "<?xml version=\"1.0\"?>\n"
1041 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1042 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1048 /* with response code and response message
1049 * also allocate enough memory */
1052 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
1053 const char * respmsg
,
1058 h
->res_buf_alloclen
< ((int)sizeof(httpresphead
) + 256 + bodylen
)) {
1061 templen
= sizeof(httpresphead
) + 256 + bodylen
;
1062 h
->res_buf
= (char *)malloc(templen
);
1064 syslog(LOG_ERR
, "malloc error in BuildHeader_upnphttp()");
1067 h
->res_buf_alloclen
= templen
;
1070 h
->res_buflen
= snprintf(h
->res_buf
, h
->res_buf_alloclen
,
1071 httpresphead
, h
->HttpVer
,
1073 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml; charset=\"utf-8\"",
1075 /* Content-Type MUST be 'text/xml; charset="utf-8"' according to UDA v1.1 */
1076 /* Content-Type MUST be 'text/xml' according to UDA v1.0 */
1077 /* Additional headers */
1078 #ifdef ENABLE_HTTP_DATE
1085 /* %a and %b depend on locale */
1086 strftime(http_date
, sizeof(http_date
),
1087 "%a, %d %b %Y %H:%M:%S GMT", &tm
);
1088 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1089 h
->res_buf_alloclen
- h
->res_buflen
,
1090 "Date: %s\r\n", http_date
);
1093 #ifdef ENABLE_EVENTS
1094 if(h
->respflags
& FLAG_TIMEOUT
) {
1095 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1096 h
->res_buf_alloclen
- h
->res_buflen
,
1097 "Timeout: Second-");
1098 if(h
->req_Timeout
) {
1099 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1100 h
->res_buf_alloclen
- h
->res_buflen
,
1101 "%d\r\n", h
->req_Timeout
);
1103 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1104 h
->res_buf_alloclen
- h
->res_buflen
,
1108 if(h
->respflags
& FLAG_SID
) {
1109 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1110 h
->res_buf_alloclen
- h
->res_buflen
,
1111 "SID: %s\r\n", h
->res_SID
);
1114 if(h
->respflags
& FLAG_ALLOW_POST
) {
1115 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1116 h
->res_buf_alloclen
- h
->res_buflen
,
1117 "Allow: %s\r\n", "POST");
1118 } else if(h
->respflags
& FLAG_ALLOW_SUB_UNSUB
) {
1119 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1120 h
->res_buf_alloclen
- h
->res_buflen
,
1121 "Allow: %s\r\n", "SUBSCRIBE, UNSUBSCRIBE");
1123 if(h
->accept_language
[0] != '\0') {
1124 /* defaulting to "en" */
1125 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1126 h
->res_buf_alloclen
- h
->res_buflen
,
1127 "Content-Language: %s\r\n",
1128 h
->accept_language
[0] == '*' ? "en" : h
->accept_language
);
1130 h
->res_buf
[h
->res_buflen
++] = '\r';
1131 h
->res_buf
[h
->res_buflen
++] = '\n';
1132 if(h
->res_buf_alloclen
< (h
->res_buflen
+ bodylen
))
1135 tmp
= (char *)realloc(h
->res_buf
, (h
->res_buflen
+ bodylen
));
1139 h
->res_buf_alloclen
= h
->res_buflen
+ bodylen
;
1143 syslog(LOG_ERR
, "realloc error in BuildHeader_upnphttp()");
1151 BuildResp2_upnphttp(struct upnphttp
* h
, int respcode
,
1152 const char * respmsg
,
1153 const char * body
, int bodylen
)
1155 int r
= BuildHeader_upnphttp(h
, respcode
, respmsg
, bodylen
);
1156 if(body
&& (r
>= 0)) {
1157 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
1158 h
->res_buflen
+= bodylen
;
1162 /* responding 200 OK ! */
1164 BuildResp_upnphttp(struct upnphttp
* h
,
1165 const char * body
, int bodylen
)
1167 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
1171 SendResp_upnphttp(struct upnphttp
* h
)
1175 while (h
->res_sent
< h
->res_buflen
)
1179 n
= SSL_write(h
->ssl
, h
->res_buf
+ h
->res_sent
,
1180 h
->res_buflen
- h
->res_sent
);
1182 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1183 h
->res_buflen
- h
->res_sent
, 0);
1186 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1187 h
->res_buflen
- h
->res_sent
, 0);
1194 err
= SSL_get_error(h
->ssl
, n
);
1195 if(err
== SSL_ERROR_WANT_READ
|| err
== SSL_ERROR_WANT_WRITE
) {
1196 /* try again later */
1199 syslog(LOG_ERR
, "SSL_write() failed");
1205 continue; /* try again immediatly */
1206 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
)
1208 /* try again later */
1211 syslog(LOG_ERR
, "send(res_buf): %m");
1212 break; /* avoid infinite loop */
1219 syslog(LOG_ERR
, "send(res_buf): %d bytes sent (out of %d)",
1220 h
->res_sent
, h
->res_buflen
);
1228 return 1; /* finished */
1232 SendRespAndClose_upnphttp(struct upnphttp
* h
)
1236 while (h
->res_sent
< h
->res_buflen
)
1240 n
= SSL_write(h
->ssl
, h
->res_buf
+ h
->res_sent
,
1241 h
->res_buflen
- h
->res_sent
);
1243 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1244 h
->res_buflen
- h
->res_sent
, 0);
1247 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1248 h
->res_buflen
- h
->res_sent
, 0);
1255 err
= SSL_get_error(h
->ssl
, n
);
1256 if(err
== SSL_ERROR_WANT_READ
|| err
== SSL_ERROR_WANT_WRITE
) {
1257 /* try again later */
1258 h
->state
= ESendingAndClosing
;
1261 syslog(LOG_ERR
, "SSL_write() failed");
1263 break; /* avoid infinite loop */
1267 continue; /* try again immediatly */
1268 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
)
1270 /* try again later */
1271 h
->state
= ESendingAndClosing
;
1274 syslog(LOG_ERR
, "send(res_buf): %m");
1275 break; /* avoid infinite loop */
1282 syslog(LOG_ERR
, "send(res_buf): %d bytes sent (out of %d)",
1283 h
->res_sent
, h
->res_buflen
);
1291 CloseSocket_upnphttp(h
);