1 /* $Id: upnphttp.c,v 1.103 2015/12/16 10:21:49 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((h
->req_CallbackOff
<= 0) && (h
->req_SIDOff
<= 0)) {
615 /* Missing or invalid CALLBACK : 412 Precondition Failed.
616 * If CALLBACK header is missing or does not contain a valid HTTP URL,
617 * the publisher must respond with HTTP error 412 Precondition Failed*/
618 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
619 SendRespAndClose_upnphttp(h
);
621 /* - add to the subscriber list
622 * - respond HTTP/x.x 200 OK
623 * - Send the initial event message */
624 /* Server:, SID:; Timeout: Second-(xx|infinite) */
625 /* Check that the callback URL is on the same IP as
626 * the request, and not on the internet, nor on ourself (DOS attack ?) */
627 if(h
->req_CallbackOff
> 0) {
629 /* SID: and Callback: are incompatible */
630 if(h
->req_SIDOff
> 0) {
631 syslog(LOG_WARNING
, "Both Callback: and SID: in SUBSCRIBE");
632 BuildResp2_upnphttp(h
, 400, "Incompatible header fields", 0, 0);
633 /* "NT: upnp:event" header is mandatory */
634 } else if(h
->req_NTOff
<= 0 || h
->req_NTLen
!= 10 ||
635 0 != memcmp("upnp:event", h
->req_buf
+ h
->req_NTOff
, 10)) {
636 syslog(LOG_WARNING
, "Invalid NT in SUBSCRIBE %.*s",
637 h
->req_NTLen
, h
->req_buf
+ h
->req_NTOff
);
638 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
641 if(checkCallbackURL(h
)) {
642 sid
= upnpevents_addSubscriber(path
, h
->req_buf
+ h
->req_CallbackOff
,
643 h
->req_CallbackLen
, h
->req_Timeout
);
644 h
->respflags
= FLAG_TIMEOUT
;
646 syslog(LOG_DEBUG
, "generated sid=%s", sid
);
647 h
->respflags
|= FLAG_SID
;
650 BuildResp_upnphttp(h
, 0, 0);
652 syslog(LOG_WARNING
, "Invalid Callback in SUBSCRIBE %.*s",
653 h
->req_CallbackLen
, h
->req_buf
+ h
->req_CallbackOff
);
654 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
657 /* subscription renew */
659 412 Precondition Failed. If a SID does not correspond to a known,
660 un-expired subscription, the publisher must respond
661 with HTTP error 412 Precondition Failed. */
663 /* SID: and NT: headers are incompatibles */
664 if(h
->req_NTOff
> 0) {
665 syslog(LOG_WARNING
, "Both NT: and SID: in SUBSCRIBE");
666 BuildResp2_upnphttp(h
, 400, "Incompatible header fields", 0, 0);
669 sid
= upnpevents_renewSubscription(h
->req_buf
+ h
->req_SIDOff
,
670 h
->req_SIDLen
, h
->req_Timeout
);
672 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
674 h
->respflags
= FLAG_TIMEOUT
| FLAG_SID
;
676 BuildResp_upnphttp(h
, 0, 0);
679 SendRespAndClose_upnphttp(h
);
684 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
686 syslog(LOG_DEBUG
, "ProcessHTTPUnSubscribe %s", path
);
687 syslog(LOG_DEBUG
, "SID '%.*s'", h
->req_SIDLen
, h
->req_buf
+ h
->req_SIDOff
);
688 /* Remove from the list */
690 if(h
->req_SIDOff
<= 0 || h
->req_SIDLen
== 0) {
691 /* SID: header missing or empty */
692 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
693 } else if(h
->req_CallbackOff
> 0 || h
->req_NTOff
> 0) {
694 /* no NT: or Callback: header must be present */
695 BuildResp2_upnphttp(h
, 400, "Incompatible header fields", 0, 0);
698 if(upnpevents_removeSubscriber(h
->req_buf
+ h
->req_SIDOff
, h
->req_SIDLen
) < 0) {
699 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
701 BuildResp_upnphttp(h
, 0, 0);
703 SendRespAndClose_upnphttp(h
);
707 /* Parse and process Http Query
708 * called once all the HTTP headers have been received,
709 * so it is guaranteed that h->req_buf contains the \r\n\r\n
710 * character sequence */
712 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
714 static const struct {
718 { ROOTDESC_PATH
, genRootDesc
},
719 { WANIPC_PATH
, genWANIPCn
},
720 { WANCFG_PATH
, genWANCfg
},
721 #ifdef HAS_DUMMY_SERVICE
724 #ifdef ENABLE_L3F_SERVICE
727 #ifdef ENABLE_6FC_SERVICE
728 { WANIP6FC_PATH
, gen6FC
},
730 #ifdef ENABLE_DP_SERVICE
735 char HttpCommand
[16];
744 /* note : checking (*p != '\r') is enough to avoid runing off the
745 * end of the buffer, because h->req_buf is guaranteed to contain
746 * the \r\n\r\n character sequence */
747 for(i
= 0; i
<15 && *p
!= ' ' && *p
!= '\r'; i
++)
748 HttpCommand
[i
] = *(p
++);
749 HttpCommand
[i
] = '\0';
752 for(i
= 0; i
<127 && *p
!= ' ' && *p
!= '\r'; i
++)
757 HttpVer
= h
->HttpVer
;
758 for(i
= 0; i
<15 && *p
!= '\r'; i
++)
761 syslog(LOG_INFO
, "HTTP REQUEST from %s : %s %s (%s)",
762 h
->clientaddr_str
, HttpCommand
, HttpUrl
, HttpVer
);
764 if(h
->req_HostOff
> 0 && h
->req_HostLen
> 0) {
765 syslog(LOG_DEBUG
, "Host: %.*s", h
->req_HostLen
, h
->req_buf
+ h
->req_HostOff
);
766 p
= h
->req_buf
+ h
->req_HostOff
;
770 while(p
< h
->req_buf
+ h
->req_HostOff
+ h
->req_HostLen
) {
772 /* TODO check *p in [0-9a-f:.] */
776 syslog(LOG_NOTICE
, "DNS rebinding attack suspected (Host: %.*s)", h
->req_HostLen
, h
->req_buf
+ h
->req_HostOff
);
781 /* TODO : Check port */
783 for(i
= 0; i
< h
->req_HostLen
; i
++) {
784 if(*p
!= ':' && *p
!= '.' && (*p
> '9' || *p
< '0')) {
785 syslog(LOG_NOTICE
, "DNS rebinding attack suspected (Host: %.*s)", h
->req_HostLen
, h
->req_buf
+ h
->req_HostOff
);
793 if(strcmp("POST", HttpCommand
) == 0)
795 h
->req_command
= EPost
;
796 ProcessHTTPPOST_upnphttp(h
);
798 else if(strcmp("GET", HttpCommand
) == 0)
800 h
->req_command
= EGet
;
801 for(i
=0; path_desc
[i
].path
; i
++) {
802 if(strcasecmp(path_desc
[i
].path
, HttpUrl
) == 0) {
804 sendXMLdesc(h
, path_desc
[i
].f
);
806 #ifdef HAS_DUMMY_SERVICE
814 if(0 == memcmp(HttpUrl
, "/ctl/", 5)) {
815 /* 405 Method Not Allowed
817 h
->respflags
= FLAG_ALLOW_POST
;
822 if(0 == memcmp(HttpUrl
, "/evt/", 5)) {
823 /* 405 Method Not Allowed
824 * Allow: SUBSCRIBE, UNSUBSCRIBE */
825 h
->respflags
= FLAG_ALLOW_SUB_UNSUB
;
830 syslog(LOG_NOTICE
, "%s not found, responding ERROR 404", HttpUrl
);
834 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
836 h
->req_command
= ESubscribe
;
837 ProcessHTTPSubscribe_upnphttp(h
, HttpUrl
);
839 else if(strcmp("UNSUBSCRIBE", HttpCommand
) == 0)
841 h
->req_command
= EUnSubscribe
;
842 ProcessHTTPUnSubscribe_upnphttp(h
, HttpUrl
);
845 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
847 syslog(LOG_NOTICE
, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled");
853 syslog(LOG_NOTICE
, "Unsupported HTTP Command %s", HttpCommand
);
860 Process_upnphttp(struct upnphttp
* h
)
870 case EWaitingForHttpRequest
:
873 n
= SSL_read(h
->ssl
, buf
, sizeof(buf
));
875 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
878 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
885 err
= SSL_get_error(h
->ssl
, n
);
886 if(err
!= SSL_ERROR_WANT_READ
&& err
!= SSL_ERROR_WANT_WRITE
)
888 syslog(LOG_ERR
, "SSL_read() failed");
890 h
->state
= EToDelete
;
894 if(errno
!= EAGAIN
&&
895 errno
!= EWOULDBLOCK
&&
898 syslog(LOG_ERR
, "recv (state0): %m");
899 h
->state
= EToDelete
;
901 /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */
908 syslog(LOG_WARNING
, "HTTP Connection from %s closed unexpectedly", inet_ntoa(h
->clientaddr
));
909 h
->state
= EToDelete
;
913 const char * endheaders
;
914 /* if 1st arg of realloc() is null,
915 * realloc behaves the same as malloc() */
916 h_tmp
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
+ 1);
919 syslog(LOG_WARNING
, "Unable to allocate new memory for h->req_buf)");
920 h
->state
= EToDelete
;
925 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
927 h
->req_buf
[h
->req_buflen
] = '\0';
929 /* search for the string "\r\n\r\n" */
930 endheaders
= findendheaders(h
->req_buf
, h
->req_buflen
);
933 /* at this point, the request buffer (h->req_buf)
934 * is guaranteed to contain the \r\n\r\n character sequence */
935 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
936 ProcessHttpQuery_upnphttp(h
);
940 case EWaitingForHttpContent
:
943 n
= SSL_read(h
->ssl
, buf
, sizeof(buf
));
945 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
948 n
= recv(h
->socket
, buf
, sizeof(buf
), 0);
955 err
= SSL_get_error(h
->ssl
, n
);
956 if(err
!= SSL_ERROR_WANT_READ
&& err
!= SSL_ERROR_WANT_WRITE
)
958 syslog(LOG_ERR
, "SSL_read() failed");
960 h
->state
= EToDelete
;
964 if(errno
!= EAGAIN
&&
965 errno
!= EWOULDBLOCK
&&
968 syslog(LOG_ERR
, "recv (state1): %m");
969 h
->state
= EToDelete
;
971 /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */
978 syslog(LOG_WARNING
, "HTTP Connection from %s closed unexpectedly", inet_ntoa(h
->clientaddr
));
979 h
->state
= EToDelete
;
983 void * tmp
= realloc(h
->req_buf
, n
+ h
->req_buflen
);
986 syslog(LOG_ERR
, "memory allocation error %m");
987 h
->state
= EToDelete
;
992 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
994 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
996 ProcessHTTPPOST_upnphttp(h
);
1001 case ESendingContinue
:
1002 if(SendResp_upnphttp(h
))
1003 h
->state
= EWaitingForHttpContent
;
1005 case ESendingAndClosing
:
1006 SendRespAndClose_upnphttp(h
);
1009 syslog(LOG_WARNING
, "Unexpected state: %d", h
->state
);
1013 static const char httpresphead
[] =
1015 "Content-Type: %s\r\n"
1016 "Connection: close\r\n"
1017 "Content-Length: %d\r\n"
1018 "Server: " MINIUPNPD_SERVER_STRING
"\r\n"
1022 "<?xml version=\"1.0\"?>\n"
1023 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1024 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1030 /* with response code and response message
1031 * also allocate enough memory */
1034 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
1035 const char * respmsg
,
1040 h
->res_buf_alloclen
< ((int)sizeof(httpresphead
) + 256 + bodylen
)) {
1043 templen
= sizeof(httpresphead
) + 256 + bodylen
;
1044 h
->res_buf
= (char *)malloc(templen
);
1046 syslog(LOG_ERR
, "malloc error in BuildHeader_upnphttp()");
1049 h
->res_buf_alloclen
= templen
;
1052 h
->res_buflen
= snprintf(h
->res_buf
, h
->res_buf_alloclen
,
1053 httpresphead
, h
->HttpVer
,
1055 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml; charset=\"utf-8\"",
1057 /* Content-Type MUST be 'text/xml; charset="utf-8"' according to UDA v1.1 */
1058 /* Content-Type MUST be 'text/xml' according to UDA v1.0 */
1059 /* Additional headers */
1060 #ifdef ENABLE_HTTP_DATE
1067 /* %a and %b depend on locale */
1068 strftime(http_date
, sizeof(http_date
),
1069 "%a, %d %b %Y %H:%M:%S GMT", &tm
);
1070 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1071 h
->res_buf_alloclen
- h
->res_buflen
,
1072 "Date: %s\r\n", http_date
);
1075 #ifdef ENABLE_EVENTS
1076 if(h
->respflags
& FLAG_TIMEOUT
) {
1077 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1078 h
->res_buf_alloclen
- h
->res_buflen
,
1079 "Timeout: Second-");
1080 if(h
->req_Timeout
) {
1081 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1082 h
->res_buf_alloclen
- h
->res_buflen
,
1083 "%d\r\n", h
->req_Timeout
);
1085 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1086 h
->res_buf_alloclen
- h
->res_buflen
,
1090 if(h
->respflags
& FLAG_SID
) {
1091 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1092 h
->res_buf_alloclen
- h
->res_buflen
,
1093 "SID: %s\r\n", h
->res_SID
);
1096 if(h
->respflags
& FLAG_ALLOW_POST
) {
1097 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1098 h
->res_buf_alloclen
- h
->res_buflen
,
1099 "Allow: %s\r\n", "POST");
1100 } else if(h
->respflags
& FLAG_ALLOW_SUB_UNSUB
) {
1101 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1102 h
->res_buf_alloclen
- h
->res_buflen
,
1103 "Allow: %s\r\n", "SUBSCRIBE, UNSUBSCRIBE");
1105 if(h
->accept_language
[0] != '\0') {
1106 /* defaulting to "en" */
1107 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1108 h
->res_buf_alloclen
- h
->res_buflen
,
1109 "Content-Language: %s\r\n",
1110 h
->accept_language
[0] == '*' ? "en" : h
->accept_language
);
1112 h
->res_buf
[h
->res_buflen
++] = '\r';
1113 h
->res_buf
[h
->res_buflen
++] = '\n';
1114 if(h
->res_buf_alloclen
< (h
->res_buflen
+ bodylen
))
1117 tmp
= (char *)realloc(h
->res_buf
, (h
->res_buflen
+ bodylen
));
1121 h
->res_buf_alloclen
= h
->res_buflen
+ bodylen
;
1125 syslog(LOG_ERR
, "realloc error in BuildHeader_upnphttp()");
1133 BuildResp2_upnphttp(struct upnphttp
* h
, int respcode
,
1134 const char * respmsg
,
1135 const char * body
, int bodylen
)
1137 int r
= BuildHeader_upnphttp(h
, respcode
, respmsg
, bodylen
);
1138 if(body
&& (r
>= 0)) {
1139 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
1140 h
->res_buflen
+= bodylen
;
1144 /* responding 200 OK ! */
1146 BuildResp_upnphttp(struct upnphttp
* h
,
1147 const char * body
, int bodylen
)
1149 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
1153 SendResp_upnphttp(struct upnphttp
* h
)
1157 while (h
->res_sent
< h
->res_buflen
)
1161 n
= SSL_write(h
->ssl
, h
->res_buf
+ h
->res_sent
,
1162 h
->res_buflen
- h
->res_sent
);
1164 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1165 h
->res_buflen
- h
->res_sent
, 0);
1168 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1169 h
->res_buflen
- h
->res_sent
, 0);
1176 err
= SSL_get_error(h
->ssl
, n
);
1177 if(err
== SSL_ERROR_WANT_READ
|| err
== SSL_ERROR_WANT_WRITE
) {
1178 /* try again later */
1181 syslog(LOG_ERR
, "SSL_write() failed");
1187 continue; /* try again immediatly */
1188 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
)
1190 /* try again later */
1193 syslog(LOG_ERR
, "send(res_buf): %m");
1194 break; /* avoid infinite loop */
1201 syslog(LOG_ERR
, "send(res_buf): %d bytes sent (out of %d)",
1202 h
->res_sent
, h
->res_buflen
);
1210 return 1; /* finished */
1214 SendRespAndClose_upnphttp(struct upnphttp
* h
)
1218 while (h
->res_sent
< h
->res_buflen
)
1222 n
= SSL_write(h
->ssl
, h
->res_buf
+ h
->res_sent
,
1223 h
->res_buflen
- h
->res_sent
);
1225 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1226 h
->res_buflen
- h
->res_sent
, 0);
1229 n
= send(h
->socket
, h
->res_buf
+ h
->res_sent
,
1230 h
->res_buflen
- h
->res_sent
, 0);
1237 err
= SSL_get_error(h
->ssl
, n
);
1238 if(err
== SSL_ERROR_WANT_READ
|| err
== SSL_ERROR_WANT_WRITE
) {
1239 /* try again later */
1240 h
->state
= ESendingAndClosing
;
1243 syslog(LOG_ERR
, "SSL_write() failed");
1245 break; /* avoid infinite loop */
1249 continue; /* try again immediatly */
1250 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
)
1252 /* try again later */
1253 h
->state
= ESendingAndClosing
;
1256 syslog(LOG_ERR
, "send(res_buf): %m");
1257 break; /* avoid infinite loop */
1264 syslog(LOG_ERR
, "send(res_buf): %d bytes sent (out of %d)",
1265 h
->res_sent
, h
->res_buflen
);
1273 CloseSocket_upnphttp(h
);