/src/open62541/src/server/ua_transport_http.c
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer) |
6 | | */ |
7 | | |
8 | | #include "ua_server_internal.h" |
9 | | #include "ua_services.h" |
10 | | |
11 | | typedef struct UA_HttpProtocolManager UA_HttpProtocolManager; |
12 | | typedef struct UA_HttpListener UA_HttpListener; |
13 | | typedef struct UA_HttpServerSequence UA_HttpServerSequence; |
14 | | |
15 | 77.1k | #define UA_HTTP_LISTENERS_SIZE 2 |
16 | | |
17 | | struct UA_HttpProtocolManager { |
18 | | UA_Driver drv; |
19 | | UA_ConnectionManager *connectionManager; |
20 | | |
21 | | /* One listener for opc.http and one for opc.https. Binary uses the |
22 | | * configured path and JSON its derived /json child route. */ |
23 | | struct UA_HttpListener { |
24 | | UA_HttpProtocolManager *manager; |
25 | | uintptr_t connectionId; |
26 | | UA_String endpointUrl; |
27 | | UA_Boolean discoveryUrlAdded; |
28 | | } listeners[UA_HTTP_LISTENERS_SIZE]; |
29 | | |
30 | | LIST_HEAD(, UA_HttpServerSequence) sequences; |
31 | | }; |
32 | | |
33 | | /* Part 6 describes one URL-level SecureChannel. Internally we keep routing |
34 | | * channels below that transport endpoint so a Session, its nonces and its |
35 | | * AuthenticationToken never become shared across unrelated clients. */ |
36 | | |
37 | | /* HTTP/1.1 requests are strictly serial on an accepted connection. The |
38 | | * ConnectionManager id therefore identifies both the sequence and its one |
39 | | * active service response. */ |
40 | | struct UA_HttpServerSequence { |
41 | | LIST_ENTRY(UA_HttpServerSequence) next; |
42 | | UA_SecureChannel *channel; |
43 | | uintptr_t connectionId; |
44 | | UA_UInt32 requestHandle; |
45 | | }; |
46 | | |
47 | | /* URL and endpoint configuration */ |
48 | | |
49 | | static UA_Boolean |
50 | 0 | isSecureHttpListener(const UA_HttpListener *listener) { |
51 | 0 | return listener == &listener->manager->listeners[1]; |
52 | 0 | } |
53 | | |
54 | | static UA_StatusCode |
55 | | parseHttpServerUrl(const UA_String *url, UA_Boolean *secure, |
56 | 0 | UA_String *hostname, UA_UInt16 *port, UA_String *path) { |
57 | 0 | if(!getHttpUrlSecurity(url, secure)) |
58 | 0 | return UA_STATUSCODE_BADNOTSUPPORTED; |
59 | 0 | *port = *secure ? 443 : 80; |
60 | 0 | UA_StatusCode res = UA_parseEndpointUrl(url, hostname, port, path); |
61 | 0 | if(res != UA_STATUSCODE_GOOD) |
62 | 0 | return res; |
63 | 0 | if(hostname->length == 0) |
64 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
65 | | |
66 | | /* Normalize the configured Binary route. */ |
67 | 0 | size_t begin = 0; |
68 | 0 | while(begin < path->length && path->data[begin] == '/') |
69 | 0 | begin++; |
70 | 0 | size_t end = path->length; |
71 | 0 | while(end > begin && path->data[end - 1] == '/') |
72 | 0 | end--; |
73 | 0 | path->data = path->data ? &path->data[begin] : NULL; |
74 | 0 | path->length = end - begin; |
75 | | |
76 | | /* Keep the derived JSON child route unambiguous. */ |
77 | 0 | static const UA_String json = UA_STRING_STATIC("json"); |
78 | 0 | if(path->length >= json.length) { |
79 | 0 | UA_String suffix = {json.length, |
80 | 0 | &path->data[path->length - json.length]}; |
81 | 0 | if(UA_String_equal(&suffix, &json) && |
82 | 0 | (path->length == json.length || |
83 | 0 | path->data[path->length - json.length - 1] == '/')) |
84 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
85 | 0 | } |
86 | 0 | return UA_STATUSCODE_GOOD; |
87 | 0 | } |
88 | | |
89 | | UA_StatusCode |
90 | 270 | UA_HttpProtocolManager_validateConfig(UA_Driver *drv) { |
91 | 270 | UA_ServerConfig *config = &drv->server->config; |
92 | 270 | if(!config->httpEnabled) |
93 | 270 | return UA_STATUSCODE_GOOD; |
94 | | |
95 | 0 | UA_Boolean haveSecureUrl = false; |
96 | 0 | UA_Boolean haveInsecureUrl = false; |
97 | 0 | for(size_t i = 0; i < config->serverUrlsSize; i++) { |
98 | 0 | const UA_String *url = &config->serverUrls[i]; |
99 | 0 | UA_Boolean secure = false; |
100 | 0 | UA_String hostname = UA_STRING_NULL; |
101 | 0 | UA_String path = UA_STRING_NULL; |
102 | 0 | UA_UInt16 port = 0; |
103 | 0 | UA_StatusCode res = parseHttpServerUrl( |
104 | 0 | url, &secure, &hostname, &port, &path); |
105 | 0 | if(res == UA_STATUSCODE_BADNOTSUPPORTED) |
106 | 0 | continue; |
107 | | |
108 | 0 | if((secure && haveSecureUrl) || (!secure && haveInsecureUrl)) { |
109 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
110 | 0 | "Only one opc.http and one opc.https ServerUrl can " |
111 | 0 | "be configured"); |
112 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
113 | 0 | } |
114 | 0 | haveSecureUrl |= secure; |
115 | 0 | haveInsecureUrl |= !secure; |
116 | |
|
117 | 0 | if(res != UA_STATUSCODE_GOOD) { |
118 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
119 | 0 | "An opc.http(s) ServerUrl requires an advertised " |
120 | 0 | "hostname; use httpListenAddress for wildcard binding"); |
121 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
122 | 0 | } |
123 | 0 | } |
124 | | |
125 | 0 | if(!haveSecureUrl && !haveInsecureUrl) { |
126 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
127 | 0 | "HTTP transport is enabled but no opc.http:// or " |
128 | 0 | "opc.https:// ServerUrl is configured"); |
129 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
130 | 0 | } |
131 | 0 | const UA_String protocol = UA_STRING_STATIC("http"); |
132 | 0 | if(!findConnectionManager(config->eventLoop, &protocol)) { |
133 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
134 | 0 | "HTTP transport is enabled but no HTTP " |
135 | 0 | "ConnectionManager is configured"); |
136 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
137 | 0 | } |
138 | 0 | if(haveInsecureUrl) { |
139 | 0 | if(!config->httpAllowUnencrypted) { |
140 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
141 | 0 | "opc.http:// is configured, but httpAllowUnencrypted " |
142 | 0 | "is false. Unencrypted HTTP transport is non-standard " |
143 | 0 | "and must be explicitly allowed"); |
144 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
145 | 0 | } |
146 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
147 | 0 | "opc.http:// is enabled. Unencrypted HTTP transport is " |
148 | 0 | "non-standard and should only be used for " |
149 | 0 | "testing/debugging"); |
150 | 0 | if(config->allowNonePolicyPassword) |
151 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
152 | 0 | "Plaintext username/password authentication is " |
153 | 0 | "explicitly enabled via allowNonePolicyPassword"); |
154 | 0 | } |
155 | 0 | if(haveSecureUrl && (config->httpCertificate.length == 0 || |
156 | 0 | config->httpPrivateKey.length == 0)) { |
157 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
158 | 0 | "HTTP transport is enabled for opc.https:// but its TLS " |
159 | 0 | "certificate or private key is empty"); |
160 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
161 | 0 | } |
162 | 0 | return UA_STATUSCODE_GOOD; |
163 | 0 | } |
164 | | |
165 | | static UA_StatusCode |
166 | | formatEndpointUrl(const UA_HttpListener *listener, const UA_String *hostname, |
167 | | UA_UInt16 port, const UA_String *path, |
168 | 0 | UA_String *endpointUrl) { |
169 | 0 | UA_Boolean ipv6 = memchr(hostname->data, ':', hostname->length) != NULL; |
170 | 0 | const char *scheme = isSecureHttpListener(listener) ? |
171 | 0 | "opc.https" : "opc.http"; |
172 | 0 | if(ipv6) |
173 | 0 | return UA_String_format(endpointUrl, "%s://[%S]:%u/%S", scheme, |
174 | 0 | *hostname, (unsigned)port, *path); |
175 | 0 | return UA_String_format(endpointUrl, "%s://%S:%u/%S", scheme, |
176 | 0 | *hostname, (unsigned)port, *path); |
177 | 0 | } |
178 | | |
179 | | static void |
180 | 25.1k | unregisterHttpDiscoveryUrl(UA_HttpListener *listener) { |
181 | 25.1k | if(!listener->discoveryUrlAdded) |
182 | 25.1k | return; |
183 | 0 | removeServerDiscoveryUrl(listener->manager->drv.server, |
184 | 0 | &listener->endpointUrl); |
185 | 0 | listener->discoveryUrlAdded = false; |
186 | 0 | } |
187 | | |
188 | | static UA_StatusCode |
189 | 0 | setEndpointUrl(UA_HttpListener *listener, UA_UInt16 port) { |
190 | 0 | UA_String hostname = UA_STRING_NULL; |
191 | 0 | UA_String path = UA_STRING_NULL; |
192 | 0 | UA_UInt16 oldPort = 0; |
193 | 0 | UA_StatusCode res = UA_parseEndpointUrl(&listener->endpointUrl, &hostname, |
194 | 0 | &oldPort, &path); |
195 | 0 | if(res != UA_STATUSCODE_GOOD) |
196 | 0 | return res; |
197 | 0 | UA_String endpointUrl = UA_STRING_NULL; |
198 | 0 | res = formatEndpointUrl(listener, &hostname, port, &path, &endpointUrl); |
199 | 0 | if(res != UA_STATUSCODE_GOOD) |
200 | 0 | return res; |
201 | | |
202 | 0 | unregisterHttpDiscoveryUrl(listener); |
203 | 0 | UA_String_clear(&listener->endpointUrl); |
204 | 0 | listener->endpointUrl = endpointUrl; |
205 | 0 | listener->discoveryUrlAdded = addServerDiscoveryUrl( |
206 | 0 | listener->manager->drv.server, &listener->endpointUrl); |
207 | 0 | return UA_STATUSCODE_GOOD; |
208 | 0 | } |
209 | | |
210 | | static const UA_String *getStringParam(const UA_KeyValueMap *params, |
211 | 0 | const char *name) { |
212 | 0 | return (const UA_String *)UA_KeyValueMap_getScalar( |
213 | 0 | params, UA_QUALIFIEDNAME(0, (char *)(uintptr_t)name), |
214 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
215 | 0 | } |
216 | | |
217 | | /* SecureChannel and request-sequence lifecycle */ |
218 | | |
219 | | static UA_Boolean |
220 | | httpChannelMatchesListener(const UA_HttpListener *listener, |
221 | 60 | const UA_SecureChannel *channel) { |
222 | 60 | return channel->transport == UA_SECURECHANNEL_TRANSPORT_HTTP && |
223 | 0 | channel->connectionManager == listener->manager->connectionManager && |
224 | 0 | channel->connectionId != 0 && |
225 | 0 | channel->connectionId == listener->connectionId; |
226 | 60 | } |
227 | | |
228 | | static UA_SecureChannel * |
229 | | newHttpChannel(UA_HttpListener *listener, UA_SecurityPolicy *policy, |
230 | | const UA_ByteString *clientCertificate, |
231 | | UA_SecureChannelEncoding encoding, |
232 | 0 | const UA_String *remoteAddress) { |
233 | 0 | UA_HttpProtocolManager *hpm = listener->manager; |
234 | 0 | UA_SecureChannel *channel = |
235 | 0 | (UA_SecureChannel *)UA_calloc(1, sizeof(*channel)); |
236 | 0 | if(!channel) |
237 | 0 | return NULL; |
238 | 0 | UA_SecureChannel_init(channel); |
239 | 0 | channel->transport = UA_SECURECHANNEL_TRANSPORT_HTTP; |
240 | 0 | channel->encoding = encoding; |
241 | 0 | channel->connectionManager = hpm->connectionManager; |
242 | 0 | channel->connectionId = listener->connectionId; |
243 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
244 | 0 | if(remoteAddress) |
245 | 0 | res = UA_String_copy(remoteAddress, &channel->remoteAddress); |
246 | 0 | UA_MessageSecurityMode mode = UA_SecureChannel_httpSecurityMode( |
247 | 0 | isSecureHttpListener(listener)); |
248 | 0 | if(res == UA_STATUSCODE_GOOD) { |
249 | 0 | res = UA_SecureChannel_setSecurityPolicyWithoutOPN( |
250 | 0 | channel, policy, clientCertificate, mode); |
251 | 0 | } |
252 | 0 | if(res == UA_STATUSCODE_GOOD) { |
253 | 0 | channel->state = UA_SECURECHANNELSTATE_OPEN; |
254 | 0 | res = registerSecureChannel(hpm->drv.server, channel); |
255 | 0 | } |
256 | 0 | if(res != UA_STATUSCODE_GOOD) { |
257 | 0 | UA_SecureChannel_clear(channel); |
258 | 0 | UA_free(channel); |
259 | 0 | return NULL; |
260 | 0 | } |
261 | 0 | return channel; |
262 | 0 | } |
263 | | |
264 | | static void |
265 | 0 | deleteHttpChannel(UA_Server *server, UA_SecureChannel *channel) { |
266 | 0 | UA_assert(!channel->sessions); |
267 | 0 | unregisterSecureChannel(server, channel); |
268 | 0 | UA_SecureChannel_clear(channel); |
269 | 0 | UA_free(channel); |
270 | 0 | } |
271 | | |
272 | | static void |
273 | | removeHttpChannelIfUnused(UA_HttpProtocolManager *hpm, |
274 | 0 | UA_SecureChannel *channel) { |
275 | 0 | if(channel->sessions || channel->state == UA_SECURECHANNELSTATE_OPEN) |
276 | 0 | return; |
277 | | |
278 | | /* A closing logical channel stays alive until its accepted responses |
279 | | * have either completed or been abandoned. */ |
280 | 0 | UA_HttpServerSequence *sequence; |
281 | 0 | LIST_FOREACH(sequence, &hpm->sequences, next) { |
282 | 0 | if(sequence->channel == channel) |
283 | 0 | return; |
284 | 0 | } |
285 | 0 | deleteHttpChannel(hpm->drv.server, channel); |
286 | 0 | } |
287 | | |
288 | | static void |
289 | | releaseHttpSequenceRequest(UA_HttpProtocolManager *hpm, |
290 | 0 | UA_HttpServerSequence *sequence) { |
291 | 0 | UA_SecureChannel *channel = sequence->channel; |
292 | 0 | sequence->channel = NULL; |
293 | 0 | sequence->requestHandle = 0; |
294 | 0 | if(channel) |
295 | 0 | removeHttpChannelIfUnused(hpm, channel); |
296 | 0 | } |
297 | | |
298 | | static UA_StatusCode |
299 | | finishHttpSequenceResponse(UA_HttpProtocolManager *hpm, |
300 | | UA_HttpServerSequence *sequence, |
301 | 0 | UA_StatusCode result) { |
302 | 0 | uintptr_t connectionId = sequence->connectionId; |
303 | 0 | releaseHttpSequenceRequest(hpm, sequence); |
304 | 0 | if(result == UA_STATUSCODE_GOOD) |
305 | 0 | return UA_STATUSCODE_GOOD; |
306 | 0 | UA_LOG_DEBUG(hpm->drv.server->config.logging, UA_LOGCATEGORY_NETWORK, |
307 | 0 | "OPC HTTP response failed: %s", UA_StatusCode_name(result)); |
308 | 0 | hpm->connectionManager->closeConnection(hpm->connectionManager, |
309 | 0 | connectionId); |
310 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
311 | 0 | } |
312 | | |
313 | | UA_StatusCode |
314 | | sendHttpServiceResponse(UA_Server *server, UA_SecureChannel *channel, |
315 | | UA_UInt64 responseToken, |
316 | 0 | void *payload, const UA_DataType *payloadType) { |
317 | 0 | UA_HttpProtocolManager *hpm = |
318 | 0 | (UA_HttpProtocolManager *)server->httpDriver; |
319 | 0 | if(responseToken > UINTPTR_MAX) |
320 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
321 | 0 | uintptr_t connectionId = (uintptr_t)responseToken; |
322 | | |
323 | | /* Resolve the accepted connection carrying this response. */ |
324 | 0 | UA_HttpServerSequence *sequence; |
325 | 0 | LIST_FOREACH(sequence, &hpm->sequences, next) { |
326 | 0 | if(sequence->connectionId == connectionId) |
327 | 0 | break; |
328 | 0 | } |
329 | 0 | if(!sequence || sequence->channel != channel) |
330 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
331 | 0 | UA_StatusCode res = UA_SecureChannel_sendHttpResponse( |
332 | 0 | channel, connectionId, payload, payloadType); |
333 | 0 | if(res == UA_STATUSCODE_BADRESPONSETOOLARGE && |
334 | 0 | payloadType != &UA_TYPES[UA_TYPES_SERVICEFAULT]) { |
335 | 0 | UA_ServiceFault fault; |
336 | 0 | UA_ServiceFault_init(&fault); |
337 | 0 | fault.responseHeader.timestamp = |
338 | 0 | server->config.eventLoop->dateTime_now(server->config.eventLoop); |
339 | 0 | fault.responseHeader.requestHandle = sequence->requestHandle; |
340 | 0 | fault.responseHeader.serviceResult = UA_STATUSCODE_BADRESPONSETOOLARGE; |
341 | 0 | res = UA_SecureChannel_sendHttpResponse( |
342 | 0 | channel, connectionId, &fault, |
343 | 0 | &UA_TYPES[UA_TYPES_SERVICEFAULT]); |
344 | 0 | } |
345 | 0 | if(res == UA_STATUSCODE_BADNOTSUPPORTED) |
346 | 0 | res = UA_Http_sendResponse(hpm->connectionManager, connectionId, 406, |
347 | 0 | NULL, NULL, NULL); |
348 | 0 | return finishHttpSequenceResponse(hpm, sequence, res); |
349 | 0 | } |
350 | | |
351 | | void |
352 | | shutdownHttpSecureChannel(UA_Server *server, UA_SecureChannel *channel, |
353 | 0 | UA_ShutdownReason reason) { |
354 | 0 | if(!UA_SecureChannel_isConnected(channel)) |
355 | 0 | return; |
356 | 0 | UA_HttpProtocolManager *hpm = |
357 | 0 | (UA_HttpProtocolManager *)server->httpDriver; |
358 | | /* The listener is shared and must survive this logical channel. */ |
359 | 0 | channel->connectionId = 0; |
360 | 0 | UA_SecureChannel_shutdown(channel, reason); |
361 | | |
362 | | /* Detach activated Sessions and reject incomplete ones. */ |
363 | 0 | while(channel->sessions) { |
364 | 0 | UA_Session *session = channel->sessions; |
365 | 0 | if(session->state == UA_SESSIONSTATE_CLOSED) |
366 | 0 | UA_Session_detachFromSecureChannel(server, session); |
367 | 0 | else if(session->state == UA_SESSIONSTATE_ACTIVATED) |
368 | 0 | UA_Session_detachFromSecureChannel(server, session); |
369 | 0 | else |
370 | 0 | UA_Session_remove(server, session, reason); |
371 | 0 | } |
372 | 0 | removeHttpChannelIfUnused(hpm, channel); |
373 | 0 | } |
374 | | |
375 | | static void |
376 | | shutdownHttpChannels(UA_HttpProtocolManager *hpm, |
377 | | const UA_HttpListener *listener, |
378 | 810 | UA_ShutdownReason reason) { |
379 | 810 | UA_Server *server = hpm->drv.server; |
380 | 810 | UA_SecureChannel *channel, *next; |
381 | 810 | TAILQ_FOREACH_SAFE(channel, &server->channels, serverEntry, next) { |
382 | 90 | UA_Boolean matches = listener ? |
383 | 60 | httpChannelMatchesListener(listener, channel) : |
384 | 90 | channel->transport == UA_SECURECHANNEL_TRANSPORT_HTTP; |
385 | 90 | if(!matches) |
386 | 90 | continue; |
387 | 0 | if(UA_SecureChannel_isConnected(channel)) |
388 | 0 | shutdownHttpSecureChannel(server, channel, reason); |
389 | 0 | else if(reason == UA_SHUTDOWNREASON_PURGE) { |
390 | 0 | UA_assert(!channel->sessions && LIST_EMPTY(&hpm->sequences)); |
391 | 0 | deleteHttpChannel(server, channel); |
392 | 0 | } |
393 | 0 | } |
394 | 810 | } |
395 | | |
396 | | /* Session and channel routing */ |
397 | | |
398 | | static UA_SecureChannel * |
399 | | selectHttpChannel(UA_HttpListener *listener, UA_SecurityPolicy *policy, |
400 | | UA_ServiceDescription *sd, const UA_Request *request, |
401 | | UA_SecureChannelEncoding encoding, |
402 | 0 | const UA_String *remoteAddress) { |
403 | 0 | UA_HttpProtocolManager *hpm = listener->manager; |
404 | 0 | UA_Server *server = hpm->drv.server; |
405 | 0 | const UA_NodeId *token = &request->requestHeader.authenticationToken; |
406 | 0 | if(!UA_NodeId_isNull(token)) { |
407 | | /* Resolve only the routing association here. The service layer applies |
408 | | * the authoritative timeout and channel-binding checks afterwards. */ |
409 | 0 | UA_Session *session = findSessionByToken(server, token); |
410 | 0 | UA_SecureChannel *channel = session ? session->channel : NULL; |
411 | 0 | if(channel && httpChannelMatchesListener(listener, channel) && |
412 | 0 | channel->state == UA_SECURECHANNELSTATE_OPEN && |
413 | 0 | channel->securityPolicy == policy && channel->encoding == encoding) |
414 | 0 | return channel; |
415 | 0 | } |
416 | | |
417 | 0 | if(sd->requestType == &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST]) { |
418 | 0 | const UA_CreateSessionRequest *csr = |
419 | 0 | (const UA_CreateSessionRequest *)request; |
420 | 0 | if(policy->policyType != UA_SECURITYPOLICYTYPE_NONE && |
421 | 0 | csr->clientCertificate.length == 0) |
422 | 0 | return NULL; |
423 | | /* An application certificate identifies an application, not a client |
424 | | * instance. Every CreateSession therefore starts a distinct logical |
425 | | * channel. The authentication token routes later HTTP requests back |
426 | | * to this channel. */ |
427 | 0 | return newHttpChannel(listener, policy, &csr->clientCertificate, |
428 | 0 | encoding, remoteAddress); |
429 | 0 | } |
430 | | |
431 | 0 | UA_SecureChannel *channel; |
432 | 0 | TAILQ_FOREACH(channel, &server->channels, serverEntry) { |
433 | 0 | if(httpChannelMatchesListener(listener, channel) && |
434 | 0 | channel->state == UA_SECURECHANNELSTATE_OPEN && !channel->sessions && |
435 | 0 | channel->securityPolicy == policy && |
436 | 0 | channel->encoding == encoding) |
437 | 0 | return channel; |
438 | 0 | } |
439 | 0 | return newHttpChannel(listener, policy, NULL, encoding, remoteAddress); |
440 | 0 | } |
441 | | |
442 | | /* Physical HTTPS connections have an unpredictable lifetime. The opaque |
443 | | * AuthenticationToken binds later requests to the logical client channel. */ |
444 | | static UA_StatusCode |
445 | | setHttpAuthenticationToken(UA_Session *session, |
446 | | UA_CreateSessionResponse *response, |
447 | 0 | const UA_ByteString *requestRandom) { |
448 | 0 | if(!requestRandom || requestRandom->length < 32) |
449 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
450 | | |
451 | 0 | UA_NodeId token = UA_NODEID_NULL; |
452 | 0 | token.namespaceIndex = 1; |
453 | 0 | token.identifierType = UA_NODEIDTYPE_BYTESTRING; |
454 | 0 | UA_ByteString random = {32, requestRandom->data}; |
455 | 0 | UA_StatusCode res = |
456 | 0 | UA_ByteString_copy(&random, &token.identifier.byteString); |
457 | 0 | if(res != UA_STATUSCODE_GOOD) |
458 | 0 | return res; |
459 | | |
460 | 0 | UA_NodeId responseAuthenticationToken = UA_NODEID_NULL; |
461 | 0 | res = UA_NodeId_copy(&token, &responseAuthenticationToken); |
462 | 0 | if(res != UA_STATUSCODE_GOOD) { |
463 | 0 | UA_NodeId_clear(&token); |
464 | 0 | return res; |
465 | 0 | } |
466 | 0 | UA_NodeId_clear(&session->authenticationToken); |
467 | 0 | session->authenticationToken = token; |
468 | 0 | UA_NodeId_clear(&response->authenticationToken); |
469 | 0 | response->authenticationToken = responseAuthenticationToken; |
470 | 0 | return UA_STATUSCODE_GOOD; |
471 | 0 | } |
472 | | |
473 | | /* Service-message decoding */ |
474 | | |
475 | | static UA_StatusCode |
476 | | processDecodedHttpRequest(UA_HttpListener *listener, |
477 | | UA_HttpServerSequence *sequence, |
478 | | UA_SecureChannelEncoding encoding, |
479 | | UA_SecurityPolicy *policy, |
480 | | UA_ServiceDescription *sd, UA_Request *request, |
481 | | const UA_String *remoteAddress, |
482 | 0 | const UA_ByteString *requestRandom) { |
483 | 0 | UA_HttpProtocolManager *hpm = listener->manager; |
484 | 0 | UA_Server *server = hpm->drv.server; |
485 | 0 | if(sequence->channel) |
486 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
487 | 0 | UA_SecureChannel *channel = |
488 | 0 | selectHttpChannel(listener, policy, sd, request, encoding, |
489 | 0 | remoteAddress); |
490 | 0 | if(!channel) |
491 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
492 | 0 | sequence->channel = channel; |
493 | 0 | sequence->requestHandle = request->requestHeader.requestHandle; |
494 | 0 | UA_UInt64 responseToken = (UA_UInt64)sequence->connectionId; |
495 | |
|
496 | 0 | UA_Response response; |
497 | 0 | UA_Boolean done = processDecodedServiceRequest( |
498 | 0 | server, channel, responseToken, sd, request, &response); |
499 | 0 | UA_Boolean createSession = |
500 | 0 | sd->requestType == &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST]; |
501 | 0 | if(createSession) { |
502 | 0 | if(done && response.responseHeader.serviceResult == UA_STATUSCODE_GOOD) { |
503 | 0 | UA_CreateSessionResponse *csr = |
504 | 0 | (UA_CreateSessionResponse *)&response; |
505 | 0 | UA_Session *session = |
506 | 0 | getSessionByToken(server, &csr->authenticationToken); |
507 | 0 | if(!session) { |
508 | 0 | response.responseHeader.serviceResult = |
509 | 0 | UA_STATUSCODE_BADINTERNALERROR; |
510 | 0 | } else { |
511 | 0 | UA_StatusCode tokenResult = |
512 | 0 | setHttpAuthenticationToken(session, csr, requestRandom); |
513 | 0 | if(tokenResult != UA_STATUSCODE_GOOD) |
514 | 0 | UA_Session_remove(server, session, |
515 | 0 | UA_SHUTDOWNREASON_REJECT); |
516 | 0 | response.responseHeader.serviceResult = tokenResult; |
517 | 0 | } |
518 | 0 | } |
519 | 0 | if(!done || response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) |
520 | 0 | channel->state = UA_SECURECHANNELSTATE_CLOSING; |
521 | 0 | } |
522 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
523 | 0 | if(done) |
524 | 0 | res = sendResponse(server, channel, responseToken, &response, |
525 | 0 | sd->responseType); |
526 | 0 | UA_clear(&response, sd->responseType); |
527 | 0 | return res; |
528 | 0 | } |
529 | | |
530 | | static UA_StatusCode |
531 | | processHttpBody(UA_HttpListener *listener, UA_HttpServerSequence *sequence, |
532 | | UA_SecurityPolicy *policy, UA_SecureChannelEncoding encoding, |
533 | | const UA_ByteString *body, const UA_String *remoteAddress, |
534 | 0 | const UA_ByteString *requestRandom) { |
535 | 0 | UA_Server *server = listener->manager->drv.server; |
536 | 0 | if(encoding == UA_SECURECHANNEL_ENCODING_BINARY) { |
537 | | /* Binary service decoding is shared with the TCP transport. */ |
538 | 0 | UA_Request request; |
539 | 0 | UA_ServiceDescription *sd = NULL; |
540 | 0 | UA_StatusCode res = decodeBinaryServiceRequest( |
541 | 0 | server, body, &sd, &request, NULL, NULL); |
542 | 0 | if(res != UA_STATUSCODE_GOOD) |
543 | 0 | return res; |
544 | 0 | res = processDecodedHttpRequest( |
545 | 0 | listener, sequence, encoding, policy, sd, &request, |
546 | 0 | remoteAddress, requestRandom); |
547 | 0 | UA_clear(&request, sd->requestType); |
548 | 0 | return res; |
549 | 0 | } |
550 | | |
551 | | #ifndef UA_ENABLE_JSON_ENCODING |
552 | | return UA_STATUSCODE_BADNOTSUPPORTED; |
553 | | #else |
554 | 0 | if(encoding != UA_SECURECHANNEL_ENCODING_JSON) |
555 | 0 | return UA_STATUSCODE_BADNOTSUPPORTED; |
556 | | |
557 | | /* Decode the JSON service envelope and resolve its request type. */ |
558 | 0 | UA_DecodeJsonOptions options; |
559 | 0 | memset(&options, 0, sizeof(options)); |
560 | 0 | options.customTypes = serverCustomTypes(server); |
561 | 0 | UA_STACKARRAY(UA_UInt16, identity, server->namespacesSize); |
562 | 0 | for(size_t i = 0; i < server->namespacesSize; i++) |
563 | 0 | identity[i] = (UA_UInt16)i; |
564 | 0 | UA_NamespaceMapping mapping = { |
565 | 0 | server->namespaces, server->namespacesSize, |
566 | 0 | identity, server->namespacesSize, |
567 | 0 | identity, server->namespacesSize}; |
568 | 0 | options.namespaceMapping = &mapping; |
569 | 0 | UA_ExtensionObject envelope; |
570 | 0 | UA_ExtensionObject_init(&envelope); |
571 | 0 | UA_StatusCode res = |
572 | 0 | UA_decodeJson(body, &envelope, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], |
573 | 0 | &options); |
574 | 0 | if(res == UA_STATUSCODE_GOOD) { |
575 | 0 | if(envelope.encoding != UA_EXTENSIONOBJECT_DECODED || |
576 | 0 | !envelope.content.decoded.type || |
577 | 0 | !envelope.content.decoded.data) { |
578 | 0 | res = UA_STATUSCODE_BADSERVICEUNSUPPORTED; |
579 | 0 | } else { |
580 | 0 | const UA_DataType *requestType = envelope.content.decoded.type; |
581 | 0 | UA_ServiceDescription *sd = NULL; |
582 | 0 | if(requestType->binaryEncodingId.namespaceIndex == 0 && |
583 | 0 | requestType->binaryEncodingId.identifierType == |
584 | 0 | UA_NODEIDTYPE_NUMERIC) |
585 | 0 | sd = getServiceDescription( |
586 | 0 | requestType->binaryEncodingId.identifier.numeric); |
587 | 0 | if(!sd || sd->requestType != requestType) |
588 | 0 | res = UA_STATUSCODE_BADSERVICEUNSUPPORTED; |
589 | 0 | else |
590 | 0 | res = processDecodedHttpRequest( |
591 | 0 | listener, sequence, encoding, policy, sd, |
592 | 0 | (UA_Request *)envelope.content.decoded.data, |
593 | 0 | remoteAddress, requestRandom); |
594 | 0 | } |
595 | 0 | } |
596 | 0 | UA_ExtensionObject_clear(&envelope); |
597 | 0 | return res; |
598 | 0 | #endif |
599 | 0 | } |
600 | | |
601 | | /* HTTP request routing */ |
602 | | |
603 | | static UA_Boolean |
604 | 0 | stripPathSuffix(UA_String *path, const UA_String *suffix) { |
605 | 0 | if(path->length < suffix->length || |
606 | 0 | memcmp(&path->data[path->length - suffix->length], suffix->data, |
607 | 0 | suffix->length) != 0) |
608 | 0 | return false; |
609 | 0 | path->length -= suffix->length; |
610 | 0 | return true; |
611 | 0 | } |
612 | | |
613 | | /* OPC UA HTTP clients conventionally append "/discovery" while selecting an |
614 | | * endpoint. The discovery request still belongs to the configured endpoint; |
615 | | * the returned EndpointUrl remains the canonical variant URL. */ |
616 | | static UA_Boolean |
617 | | findEndpointEncoding(UA_HttpListener *listener, const UA_String *path, |
618 | 0 | UA_SecureChannelEncoding *encoding) { |
619 | 0 | static const UA_String discovery = UA_STRING_STATIC("/discovery"); |
620 | 0 | UA_String hostname = UA_STRING_NULL; |
621 | 0 | UA_String endpointPath = UA_STRING_NULL; |
622 | 0 | UA_UInt16 port = 0; |
623 | 0 | if(UA_parseEndpointUrl(&listener->endpointUrl, &hostname, &port, |
624 | 0 | &endpointPath) != UA_STATUSCODE_GOOD) |
625 | 0 | return false; |
626 | | |
627 | 0 | UA_String route = *path; |
628 | 0 | stripPathSuffix(&route, &discovery); |
629 | 0 | #ifdef UA_ENABLE_JSON_ENCODING |
630 | 0 | static const UA_String json = UA_STRING_STATIC("/json"); |
631 | 0 | *encoding = stripPathSuffix(&route, &json) ? |
632 | 0 | UA_SECURECHANNEL_ENCODING_JSON : UA_SECURECHANNEL_ENCODING_BINARY; |
633 | | #else |
634 | | *encoding = UA_SECURECHANNEL_ENCODING_BINARY; |
635 | | #endif |
636 | 0 | static const UA_String root = UA_STRING_STATIC("/"); |
637 | 0 | if(route.length == 0 && endpointPath.length == 0) |
638 | 0 | route = root; |
639 | | |
640 | | /* Match the remaining route to the configured Binary endpoint. */ |
641 | 0 | if(route.length != endpointPath.length + 1 || route.data[0] != '/') |
642 | 0 | return false; |
643 | 0 | return endpointPath.length == 0 || |
644 | 0 | memcmp(&route.data[1], endpointPath.data, |
645 | 0 | endpointPath.length) == 0; |
646 | 0 | } |
647 | | |
648 | | static UA_UInt16 |
649 | 0 | httpStatusForProcessingError(UA_StatusCode status) { |
650 | 0 | switch(status) { |
651 | 0 | case UA_STATUSCODE_BADDECODINGERROR: |
652 | 0 | case UA_STATUSCODE_BADSECURITYCHECKSFAILED: |
653 | 0 | case UA_STATUSCODE_BADSECURITYPOLICYREJECTED: |
654 | 0 | case UA_STATUSCODE_BADREQUESTTYPEINVALID: |
655 | 0 | return 400; |
656 | 0 | case UA_STATUSCODE_BADREQUESTTOOLARGE: |
657 | 0 | return 413; |
658 | 0 | case UA_STATUSCODE_BADNOTSUPPORTED: |
659 | 0 | return 406; |
660 | 0 | case UA_STATUSCODE_BADINVALIDSTATE: |
661 | 0 | case UA_STATUSCODE_BADNOTFOUND: |
662 | 0 | case UA_STATUSCODE_BADCONNECTIONCLOSED: |
663 | 0 | return 0; /* The request is gone or already responding. */ |
664 | 0 | case UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED: |
665 | | /* At this layer this can also originate while encoding the response. |
666 | | * Do not misreport an internal response failure as a client error. */ |
667 | 0 | return 500; |
668 | 0 | default: |
669 | 0 | return 500; |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | | static void |
674 | | sendHttpStatusResponse(UA_HttpProtocolManager *hpm, |
675 | | UA_HttpServerSequence *sequence, |
676 | 0 | UA_UInt16 status) { |
677 | 0 | UA_StatusCode res = UA_Http_sendResponse( |
678 | 0 | hpm->connectionManager, sequence->connectionId, status, |
679 | 0 | NULL, NULL, NULL); |
680 | 0 | (void)finishHttpSequenceResponse(hpm, sequence, res); |
681 | 0 | } |
682 | | |
683 | | static void |
684 | 270 | finishHttpStopIfDrained(UA_HttpProtocolManager *hpm) { |
685 | 270 | if(hpm->drv.state != UA_LIFECYCLESTATE_STOPPING || |
686 | 270 | !LIST_EMPTY(&hpm->sequences)) |
687 | 0 | return; |
688 | 810 | for(size_t i = 0; i < UA_HTTP_LISTENERS_SIZE; i++) { |
689 | 540 | UA_HttpListener *listener = &hpm->listeners[i]; |
690 | 540 | if(listener->connectionId != 0) |
691 | 0 | return; |
692 | 540 | } |
693 | 270 | shutdownHttpChannels(hpm, NULL, UA_SHUTDOWNREASON_PURGE); |
694 | 270 | hpm->drv.state = UA_LIFECYCLESTATE_STOPPED; |
695 | 270 | } |
696 | | |
697 | | static void |
698 | | processHttpRequest(UA_HttpListener *listener, |
699 | | UA_HttpServerSequence *sequence, |
700 | 0 | const UA_KeyValueMap *params, UA_ByteString msg) { |
701 | 0 | UA_HttpProtocolManager *hpm = listener->manager; |
702 | 0 | uintptr_t connectionId = sequence->connectionId; |
703 | 0 | const UA_String *method = getStringParam(params, "method"); |
704 | 0 | const UA_String *path = getStringParam(params, "path"); |
705 | |
|
706 | 0 | const UA_String post = UA_STRING_STATIC("POST"); |
707 | 0 | UA_Boolean duplicateContentType = false, invalidContentType = false; |
708 | 0 | const UA_String *contentType = |
709 | 0 | UA_Http_getHeader(params, "content-type", &duplicateContentType, |
710 | 0 | &invalidContentType); |
711 | 0 | UA_Boolean duplicateContentEncoding = false; |
712 | 0 | UA_Boolean invalidContentEncoding = false; |
713 | 0 | const UA_String *contentEncoding = |
714 | 0 | UA_Http_getHeader(params, "content-encoding", |
715 | 0 | &duplicateContentEncoding, |
716 | 0 | &invalidContentEncoding); |
717 | 0 | const UA_ByteString *requestRandom = |
718 | 0 | (const UA_ByteString *)UA_KeyValueMap_getScalar( |
719 | 0 | params, UA_QUALIFIEDNAME(0, "request-random"), |
720 | 0 | &UA_TYPES[UA_TYPES_BYTESTRING]); |
721 | 0 | const UA_String *remoteAddress = getStringParam(params, "remote-address"); |
722 | 0 | UA_UInt16 status = 0; |
723 | 0 | UA_SecureChannelEncoding encoding = UA_SECURECHANNEL_ENCODING_BINARY; |
724 | 0 | if(duplicateContentType || invalidContentType || |
725 | 0 | duplicateContentEncoding || invalidContentEncoding) |
726 | 0 | status = 400; |
727 | 0 | else if(!method || !UA_String_equal(method, &post)) |
728 | 0 | status = 405; |
729 | 0 | else if(!path) |
730 | 0 | status = 404; |
731 | 0 | else if(!findEndpointEncoding(listener, path, &encoding)) |
732 | 0 | status = 404; |
733 | 0 | else { |
734 | | /* Validate the selected endpoint representation. */ |
735 | 0 | if(!UA_Http_contentTypeMatchesEncoding(contentType, encoding)) { |
736 | 0 | status = 415; |
737 | 0 | } else if(contentEncoding && |
738 | 0 | !UA_Http_headerValueEquals(contentEncoding, "identity") && |
739 | 0 | (encoding == UA_SECURECHANNEL_ENCODING_BINARY || |
740 | 0 | (!UA_Http_headerValueEquals(contentEncoding, "gzip") && |
741 | 0 | !UA_Http_headerValueEquals(contentEncoding, "x-gzip")))) { |
742 | 0 | status = 415; |
743 | 0 | } |
744 | 0 | } |
745 | | |
746 | | /* The absent header selects SecurityPolicy None. Application certificate |
747 | | * validation for secure policies happens in CreateSession, above TLS. */ |
748 | 0 | UA_Boolean duplicatePolicy = false, invalidPolicy = false; |
749 | 0 | const UA_String *policyUri = |
750 | 0 | UA_Http_getHeader(params, "opcua-securitypolicy", &duplicatePolicy, |
751 | 0 | &invalidPolicy); |
752 | 0 | if(duplicatePolicy || invalidPolicy) |
753 | 0 | status = 400; |
754 | 0 | if(!policyUri) |
755 | 0 | policyUri = &UA_SECURITY_POLICY_NONE_URI; |
756 | 0 | UA_SecurityPolicy *policy = |
757 | 0 | getSecurityPolicyByUri(hpm->drv.server, policyUri); |
758 | 0 | if(!policy) |
759 | 0 | status = 400; |
760 | 0 | else if(!isSecureHttpListener(listener) && |
761 | 0 | policy->policyType != UA_SECURITYPOLICYTYPE_NONE) |
762 | 0 | status = 400; |
763 | |
|
764 | 0 | if(status != 0) { |
765 | 0 | sendHttpStatusResponse(hpm, sequence, status); |
766 | 0 | return; |
767 | 0 | } |
768 | | |
769 | 0 | UA_StatusCode res = processHttpBody( |
770 | 0 | listener, sequence, policy, encoding, &msg, |
771 | 0 | remoteAddress, requestRandom); |
772 | 0 | if(res == UA_STATUSCODE_GOOD) |
773 | 0 | return; |
774 | | |
775 | 0 | UA_LOG_DEBUG(hpm->drv.server->config.logging, UA_LOGCATEGORY_NETWORK, |
776 | 0 | "OPC HTTP request decoding or dispatch failed: %s", |
777 | 0 | UA_StatusCode_name(res)); |
778 | 0 | UA_UInt16 errorStatus = httpStatusForProcessingError(res); |
779 | 0 | if(errorStatus) |
780 | 0 | sendHttpStatusResponse(hpm, sequence, errorStatus); |
781 | 0 | else |
782 | 0 | hpm->connectionManager->closeConnection(hpm->connectionManager, |
783 | 0 | connectionId); |
784 | 0 | } |
785 | | |
786 | | /* Connection lifecycle */ |
787 | | |
788 | | static void httpNetworkCallback(UA_ConnectionManager *cm, |
789 | | uintptr_t connectionId, void *application, |
790 | | void **connectionContext, |
791 | | UA_ConnectionState state, |
792 | | const UA_KeyValueMap *params, |
793 | 0 | UA_ByteString msg) { |
794 | 0 | UA_HttpListener *listener = (UA_HttpListener *)application; |
795 | 0 | UA_HttpProtocolManager *hpm = listener->manager; |
796 | 0 | UA_assert(cm == hpm->connectionManager); |
797 | 0 | lockServer(hpm->drv.server); |
798 | |
|
799 | 0 | if(state == UA_CONNECTIONSTATE_CLOSING) { |
800 | 0 | if(listener->connectionId == connectionId) { |
801 | 0 | shutdownHttpChannels(hpm, listener, UA_SHUTDOWNREASON_CLOSE); |
802 | 0 | unregisterHttpDiscoveryUrl(listener); |
803 | 0 | listener->connectionId = 0; |
804 | 0 | } else { |
805 | 0 | UA_HttpServerSequence *sequence = |
806 | 0 | (UA_HttpServerSequence *)*connectionContext; |
807 | 0 | if(sequence && sequence->connectionId == connectionId) { |
808 | | /* Tear down the accepted sequence and abandon its response. */ |
809 | 0 | *connectionContext = NULL; |
810 | 0 | if(sequence->channel) |
811 | 0 | abandonServiceRequest( |
812 | 0 | hpm->drv.server, sequence->channel, |
813 | 0 | (UA_UInt64)sequence->connectionId); |
814 | 0 | releaseHttpSequenceRequest(hpm, sequence); |
815 | 0 | LIST_REMOVE(sequence, next); |
816 | 0 | UA_free(sequence); |
817 | 0 | } |
818 | 0 | } |
819 | 0 | finishHttpStopIfDrained(hpm); |
820 | 0 | unlockServer(hpm->drv.server); |
821 | 0 | return; |
822 | 0 | } |
823 | | |
824 | 0 | const UA_UInt16 *listenPort = |
825 | 0 | (const UA_UInt16 *)UA_KeyValueMap_getScalar( |
826 | 0 | params, UA_QUALIFIEDNAME(0, "listen-port"), |
827 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
828 | 0 | UA_Boolean listenerEvent = |
829 | 0 | (state == UA_CONNECTIONSTATE_OPENING || listenPort != NULL); |
830 | 0 | if(listenerEvent && |
831 | 0 | (listener->connectionId == 0 || |
832 | 0 | connectionId == listener->connectionId) && |
833 | 0 | (state == UA_CONNECTIONSTATE_OPENING || |
834 | 0 | state == UA_CONNECTIONSTATE_ESTABLISHED)) { |
835 | 0 | listener->connectionId = connectionId; |
836 | 0 | UA_StatusCode res = listenPort ? setEndpointUrl(listener, *listenPort) |
837 | 0 | : UA_STATUSCODE_GOOD; |
838 | 0 | if(res != UA_STATUSCODE_GOOD) { |
839 | 0 | UA_LOG_ERROR(hpm->drv.server->config.logging, |
840 | 0 | UA_LOGCATEGORY_SERVER, |
841 | 0 | "Constructing the OPC HTTP endpoint URL failed: %s", |
842 | 0 | UA_StatusCode_name(res)); |
843 | 0 | cm->closeConnection(cm, connectionId); |
844 | 0 | } |
845 | 0 | unlockServer(hpm->drv.server); |
846 | 0 | return; |
847 | 0 | } |
848 | | |
849 | 0 | if(state != UA_CONNECTIONSTATE_ESTABLISHED) { |
850 | 0 | unlockServer(hpm->drv.server); |
851 | 0 | return; |
852 | 0 | } |
853 | | |
854 | 0 | const UA_String *method = getStringParam(params, "method"); |
855 | 0 | if(!method) { |
856 | | /* Attach reusable server-side storage to the newly accepted socket |
857 | | * before its first request arrives. */ |
858 | 0 | UA_assert(*connectionContext == NULL); |
859 | 0 | UA_HttpServerSequence *sequence = |
860 | 0 | (UA_HttpServerSequence*)UA_calloc(1, sizeof(*sequence)); |
861 | 0 | if(!sequence) { |
862 | 0 | cm->closeConnection(cm, connectionId); |
863 | 0 | unlockServer(hpm->drv.server); |
864 | 0 | return; |
865 | 0 | } |
866 | 0 | sequence->connectionId = connectionId; |
867 | 0 | LIST_INSERT_HEAD(&hpm->sequences, sequence, next); |
868 | 0 | *connectionContext = sequence; |
869 | 0 | unlockServer(hpm->drv.server); |
870 | 0 | return; |
871 | 0 | } |
872 | | |
873 | 0 | UA_HttpServerSequence *sequence = |
874 | 0 | (UA_HttpServerSequence *)*connectionContext; |
875 | 0 | if(!sequence || sequence->connectionId != connectionId) { |
876 | 0 | cm->closeConnection(cm, connectionId); |
877 | 0 | unlockServer(hpm->drv.server); |
878 | 0 | return; |
879 | 0 | } |
880 | 0 | if(sequence->channel) { |
881 | 0 | UA_LOG_DEBUG(hpm->drv.server->config.logging, |
882 | 0 | UA_LOGCATEGORY_NETWORK, |
883 | 0 | "Rejecting overlapping request on HTTP connection %" PRIuPTR, |
884 | 0 | connectionId); |
885 | 0 | cm->closeConnection(cm, connectionId); |
886 | 0 | unlockServer(hpm->drv.server); |
887 | 0 | return; |
888 | 0 | } |
889 | 0 | processHttpRequest(listener, sequence, params, msg); |
890 | 0 | unlockServer(hpm->drv.server); |
891 | 0 | } |
892 | | |
893 | | /* Listener and driver lifecycle */ |
894 | | |
895 | | static UA_StatusCode |
896 | 0 | openHttpListener(UA_HttpProtocolManager *hpm, const UA_String *serverUrl) { |
897 | 0 | if(!hpm->connectionManager) |
898 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
899 | 0 | UA_String hostname = UA_STRING_NULL; |
900 | 0 | UA_String path = UA_STRING_NULL; |
901 | 0 | UA_Boolean secure = false; |
902 | 0 | UA_UInt16 port = 0; |
903 | 0 | UA_StatusCode res = parseHttpServerUrl( |
904 | 0 | serverUrl, &secure, &hostname, &port, &path); |
905 | 0 | if(res != UA_STATUSCODE_GOOD) |
906 | 0 | return res; |
907 | | |
908 | 0 | UA_HttpListener *listener = &hpm->listeners[secure ? 1 : 0]; |
909 | 0 | UA_assert(listener->endpointUrl.length == 0); |
910 | 0 | res = formatEndpointUrl(listener, &hostname, port, &path, |
911 | 0 | &listener->endpointUrl); |
912 | 0 | if(res != UA_STATUSCODE_GOOD) |
913 | 0 | return res; |
914 | | |
915 | 0 | UA_ServerConfig *config = &hpm->drv.server->config; |
916 | 0 | const UA_String *listenAddress = &hostname; |
917 | 0 | if(config->httpListenAddress.data) |
918 | 0 | listenAddress = &config->httpListenAddress; |
919 | 0 | UA_KeyValuePair p[11] = {0}; |
920 | 0 | size_t n = 0; |
921 | 0 | UA_Boolean yes = true; |
922 | 0 | #define ADD_HTTP_PARAM(NAME, VALUE, TYPE) \ |
923 | 0 | do { \ |
924 | 0 | p[n].key = UA_QUALIFIEDNAME(0, NAME); \ |
925 | 0 | UA_Variant_setScalar(&p[n++].value, VALUE, &UA_TYPES[TYPE]); \ |
926 | 0 | } while(0) |
927 | 0 | ADD_HTTP_PARAM("port", &port, UA_TYPES_UINT16); |
928 | 0 | ADD_HTTP_PARAM("listen", &yes, UA_TYPES_BOOLEAN); |
929 | 0 | ADD_HTTP_PARAM("useSSL", &secure, UA_TYPES_BOOLEAN); |
930 | 0 | ADD_HTTP_PARAM("timeout", &config->httpTimeout, UA_TYPES_UINT16); |
931 | 0 | ADD_HTTP_PARAM("recv-max-message-size", &config->httpMaxMsgSize, |
932 | 0 | UA_TYPES_UINT32); |
933 | 0 | ADD_HTTP_PARAM("recv-max-decompressed-message-size", |
934 | 0 | &config->httpMaxDecompressedMsgSize, UA_TYPES_UINT32); |
935 | 0 | ADD_HTTP_PARAM("send-max-message-size", &config->httpMaxMsgSize, |
936 | 0 | UA_TYPES_UINT32); |
937 | 0 | if(secure) { |
938 | 0 | ADD_HTTP_PARAM("certificate", &config->httpCertificate, |
939 | 0 | UA_TYPES_BYTESTRING); |
940 | 0 | ADD_HTTP_PARAM("private-key", &config->httpPrivateKey, |
941 | 0 | UA_TYPES_BYTESTRING); |
942 | 0 | } |
943 | 0 | if(listenAddress->length > 0) |
944 | 0 | ADD_HTTP_PARAM("address", (void *)(uintptr_t)listenAddress, |
945 | 0 | UA_TYPES_STRING); |
946 | 0 | if(secure && config->httpPrivateKeyPassword.length > 0) |
947 | 0 | ADD_HTTP_PARAM("private-key-password", &config->httpPrivateKeyPassword, |
948 | 0 | UA_TYPES_STRING); |
949 | 0 | #undef ADD_HTTP_PARAM |
950 | 0 | UA_KeyValueMap map = {n, p}; |
951 | 0 | res = hpm->connectionManager->openConnection( |
952 | 0 | hpm->connectionManager, &map, listener, NULL, httpNetworkCallback); |
953 | 0 | if(res != UA_STATUSCODE_GOOD) { |
954 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
955 | 0 | "Opening the OPC HTTP listener failed: %s", |
956 | 0 | UA_StatusCode_name(res)); |
957 | 0 | UA_String_clear(&listener->endpointUrl); |
958 | 0 | } |
959 | 0 | return res; |
960 | 0 | } |
961 | | |
962 | 12.5k | static void clearHttpListeners(UA_HttpProtocolManager *hpm) { |
963 | | /* Release the endpoint metadata owned by each listener. */ |
964 | 37.7k | for(size_t i = 0; i < UA_HTTP_LISTENERS_SIZE; i++) { |
965 | 25.1k | UA_HttpListener *listener = &hpm->listeners[i]; |
966 | 25.1k | UA_assert(listener->connectionId == 0); |
967 | 25.1k | unregisterHttpDiscoveryUrl(listener); |
968 | 25.1k | UA_String_clear(&listener->endpointUrl); |
969 | 25.1k | } |
970 | 12.5k | hpm->connectionManager = NULL; |
971 | 12.5k | } |
972 | | |
973 | | static void |
974 | 270 | closeHttpListeners(UA_HttpProtocolManager *hpm) { |
975 | 810 | for(size_t i = 0; i < UA_HTTP_LISTENERS_SIZE; i++) { |
976 | 540 | UA_HttpListener *listener = &hpm->listeners[i]; |
977 | 540 | if(listener->connectionId) |
978 | 0 | hpm->connectionManager->closeConnection(hpm->connectionManager, |
979 | 0 | listener->connectionId); |
980 | 540 | } |
981 | 270 | } |
982 | | |
983 | 270 | static UA_StatusCode startHttp(UA_Driver *drv) { |
984 | 270 | UA_HttpProtocolManager *hpm = (UA_HttpProtocolManager *)drv; |
985 | 270 | UA_ServerConfig *config = &drv->server->config; |
986 | | /* Listener callbacks are finished while the driver is stopped. Discard |
987 | | * their records before recreating the current configuration. */ |
988 | 270 | clearHttpListeners(hpm); |
989 | 270 | if(!config->httpEnabled) { |
990 | 270 | drv->state = UA_LIFECYCLESTATE_STARTED; |
991 | 270 | return UA_STATUSCODE_GOOD; |
992 | 270 | } |
993 | | |
994 | | /* Resolve the HTTP ConnectionManager. */ |
995 | 0 | const UA_String protocol = UA_STRING_STATIC("http"); |
996 | 0 | hpm->connectionManager = |
997 | 0 | findConnectionManager(config->eventLoop, &protocol); |
998 | 0 | if(!hpm->connectionManager) { |
999 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
1000 | 0 | "No HTTP ConnectionManager is configured"); |
1001 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1002 | 0 | } |
1003 | 0 | UA_StatusCode res = UA_STATUSCODE_BADNOTFOUND; |
1004 | 0 | for(size_t i = 0; i < config->serverUrlsSize; i++) { |
1005 | 0 | if(!getHttpUrlSecurity(&config->serverUrls[i], NULL)) |
1006 | 0 | continue; |
1007 | 0 | res = openHttpListener(hpm, &config->serverUrls[i]); |
1008 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1009 | | /* The configured listeners are one feature. Never leave a |
1010 | | * partially available OPC HTTP endpoint set behind. */ |
1011 | 0 | drv->state = UA_LIFECYCLESTATE_STOPPING; |
1012 | 0 | closeHttpListeners(hpm); |
1013 | 0 | finishHttpStopIfDrained(hpm); |
1014 | 0 | return res; |
1015 | 0 | } |
1016 | 0 | } |
1017 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1018 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_SERVER, |
1019 | 0 | "Could not open an OPC HTTP listener"); |
1020 | 0 | shutdownHttpChannels(hpm, NULL, UA_SHUTDOWNREASON_PURGE); |
1021 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1022 | 0 | } |
1023 | 0 | drv->state = UA_LIFECYCLESTATE_STARTED; |
1024 | 0 | return UA_STATUSCODE_GOOD; |
1025 | 0 | } |
1026 | | |
1027 | 270 | static void stopHttp(UA_Driver *drv) { |
1028 | 270 | UA_HttpProtocolManager *hpm = (UA_HttpProtocolManager *)drv; |
1029 | 270 | drv->state = UA_LIFECYCLESTATE_STOPPING; |
1030 | 810 | for(size_t i = 0; i < UA_HTTP_LISTENERS_SIZE; i++) |
1031 | 540 | shutdownHttpChannels(hpm, &hpm->listeners[i], |
1032 | 540 | UA_SHUTDOWNREASON_CLOSE); |
1033 | 270 | closeHttpListeners(hpm); |
1034 | 270 | finishHttpStopIfDrained(hpm); |
1035 | 270 | } |
1036 | | |
1037 | | /* startHttp opens all listeners or none. A disabled transport is started |
1038 | | * without a ConnectionManager. */ |
1039 | | UA_Boolean |
1040 | 50 | UA_HttpProtocolManager_isListening(const UA_Driver *drv) { |
1041 | 50 | const UA_HttpProtocolManager *hpm = (const UA_HttpProtocolManager *)drv; |
1042 | 50 | return drv->state == UA_LIFECYCLESTATE_STARTED && |
1043 | 50 | hpm->connectionManager != NULL; |
1044 | 50 | } |
1045 | | |
1046 | 12.3k | static UA_StatusCode freeHttp(UA_Driver *drv) { |
1047 | 12.3k | if(drv->state != UA_LIFECYCLESTATE_STOPPED) |
1048 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1049 | 12.3k | UA_HttpProtocolManager *hpm = (UA_HttpProtocolManager *)drv; |
1050 | 12.3k | clearHttpListeners(hpm); |
1051 | 12.3k | UA_free(hpm); |
1052 | 12.3k | return UA_STATUSCODE_GOOD; |
1053 | 12.3k | } |
1054 | | |
1055 | 12.3k | UA_Driver *UA_HttpProtocolManager_new(void) { |
1056 | 12.3k | UA_HttpProtocolManager *hpm = |
1057 | 12.3k | (UA_HttpProtocolManager *)UA_calloc(1, sizeof(*hpm)); |
1058 | 12.3k | if(!hpm) |
1059 | 0 | return NULL; |
1060 | 36.9k | for(size_t i = 0; i < UA_HTTP_LISTENERS_SIZE; i++) |
1061 | 24.6k | hpm->listeners[i].manager = hpm; |
1062 | 12.3k | hpm->drv.name = UA_STRING("opc-http"); |
1063 | 12.3k | hpm->drv.start = startHttp; |
1064 | 12.3k | hpm->drv.stop = stopHttp; |
1065 | 12.3k | hpm->drv.free = freeHttp; |
1066 | 12.3k | return &hpm->drv; |
1067 | 12.3k | } |