Stun Server  Compliant with the latest RFCs including 5389, 5769, and 5780
discover the local host's own external IP address
messagehandler.cpp
Go to the documentation of this file.
1 /*
2  Copyright 2011 John Selbie
3 
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 */
16 
17 
18 #include "commonincludes.hpp"
19 #include "stuncore.h"
20 #include "messagehandler.h"
21 #include "socketrole.h"
22 
23 
25 _pAuth(NULL),
26 _pAddrSet(NULL),
27 _pMsgIn(NULL),
28 _pMsgOut(NULL),
29 _integrity(), // zero-init
30 _error(), // zero-init
31 _fRequestHasResponsePort(false),
32 _transid(), // zero-init
33 _fLegacyMode(false)
34 {
35 
36 }
37 
38 
40 {
41  HRESULT hr = S_OK;
42 
43  CStunRequestHandler handler;
44 
45  // parameter checking
46  ChkIfA(msgIn.pReader==NULL, E_INVALIDARG);
48 
49  ChkIfA(msgOut.spBufferOut==NULL, E_INVALIDARG);
50  ChkIfA(msgOut.spBufferOut->GetAllocatedSize() < MAX_STUN_MESSAGE_SIZE, E_INVALIDARG);
51 
52  ChkIf(pAddressSet == NULL, E_INVALIDARG);
53 
54  // If we get something that can't be validated as a stun message, don't send back a response
55  // STUN RFC may suggest sending back a "500", but I think that's the wrong approach.
57 
58  msgOut.spBufferOut->SetSize(0);
59 
60  // build the context object to pass around this "C" type code environment
61  handler._pAuth = pAuth;
62  handler._pAddrSet = pAddressSet;
63  handler._pMsgIn = &msgIn;
64  handler._pMsgOut = &msgOut;
65 
66  // pre-prep message out
67  handler._pMsgOut->socketrole = handler._pMsgIn->socketrole; // output socket is the socket that sent us the message
68  handler._pMsgOut->addrDest = handler._pMsgIn->addrRemote; // destination address is same as source
69 
70  // now call the function that does all the real work
71  hr = handler.ProcessRequestImpl();
72 
73 Cleanup:
74  return hr;
75 }
76 
78 {
79  HRESULT hrResult = S_OK;
80  HRESULT hr = S_OK;
81 
82 
83  // aliases
84  CStunMessageReader &reader = *(_pMsgIn->pReader);
85 
86  uint16_t responseport = 0;
87 
88  // ignore anything that is not a request (with no response)
90 
91  // pre-prep the error message in case we wind up needing senderrorto send it
92  _error.msgtype = reader.GetMessageType();
94 
95 
96  reader.GetTransactionId(&_transid);
98 
99  // we always try to honor the response port
100  reader.GetResponsePort(&responseport);
101  if (responseport != 0)
102  {
104 
106  {
107  // special case for TCP - we can't do a response port for connection oriented sockets
108  // so just flag this request as an error
109  // todo - consider relaxing this check since the calling code is going to ignore the response address anyway for TCP
111  }
112  else
113  {
114  _pMsgOut->addrDest.SetPort(responseport);
115  }
116  }
117 
118 
119  if (_error.errorcode == 0)
120  {
121  if (reader.GetMessageType() != StunMsgTypeBinding)
122  {
123  // we're going to send back an error response for requests that are not binding requests
124  _error.errorcode = STUN_ERROR_BADREQUEST; // invalid request
125  }
126  }
127 
128 
129  if (_error.errorcode == 0)
130  {
131  hrResult = ValidateAuth(); // returns S_OK if _pAuth is NULL
132 
133  // if auth didn't succeed, but didn't set an error code, then setup a generic error response
134  if (FAILED(hrResult) && (_error.errorcode == 0))
135  {
137  }
138  }
139 
140 
141  if (_error.errorcode == 0)
142  {
143  hrResult = ProcessBindingRequest();
144  if (FAILED(hrResult) && (_error.errorcode == 0))
145  {
147  }
148  }
149 
150  if (_error.errorcode != 0)
151  {
153  }
154 
155 Cleanup:
156  return hr;
157 }
158 
160 {
161  CStunMessageBuilder builder;
162  CRefCountedBuffer spBuffer;
163 
164 
165  _pMsgOut->spBufferOut->SetSize(0);
166  builder.GetStream().Attach(_pMsgOut->spBufferOut, true);
167 
168  // set RFC 3478 mode if the request appears to be that way
169  builder.SetLegacyMode(_fLegacyMode);
170 
172  builder.AddTransactionId(_transid);
173  builder.AddErrorCode(_error.errorcode, "FAILED");
174 
176  {
178  }
180  {
181  if (_error.szNonce[0])
182  {
184  }
185 
186  if (_error.szRealm[0])
187  {
189  }
190  }
191 
192  builder.FixLengthField();
193  builder.GetResult(&spBuffer);
194 
195  ASSERT(spBuffer->GetSize() != 0);
196  ASSERT(spBuffer == _pMsgOut->spBufferOut);
197 
198  return;
199 }
200 
201 
203 {
204  CStunMessageReader& reader = *(_pMsgIn->pReader);
205 
206  bool fRequestHasPaddingAttribute = false;
207  SocketRole socketOutput = _pMsgIn->socketrole; // initialize to be from the socket we received from
208  StunChangeRequestAttribute changerequest = {};
209  bool fSendOtherAddress = false;
210  bool fSendOriginAddress = false;
211  SocketRole socketOther;
212  CSocketAddress addrOrigin;
213  CSocketAddress addrOther;
214  CStunMessageBuilder builder;
215  uint16_t paddingSize = 0;
216  HRESULT hrResult;
217 
218 
219  _pMsgOut->spBufferOut->SetSize(0);
220  builder.GetStream().Attach(_pMsgOut->spBufferOut, true);
221 
222  // if the client request smells like RFC 3478, then send the resposne back in the same way
223  builder.SetLegacyMode(_fLegacyMode);
224 
225  // check for an alternate response port
226  // check for padding attribute (todo - figure out how to inject padding into the response)
227  // check for a change request and validate we can do it. If so, set _socketOutput. If not, fill out _error and return.
228  // determine if we have an "other" address to notify the caller about
229 
230 
231  // did the request come with a padding request
232  if (SUCCEEDED(reader.GetPaddingAttributeSize(&paddingSize)))
233  {
234  // todo - figure out how we're going to get the MTU size of the outgoing interface
235  fRequestHasPaddingAttribute = true;
236  }
237 
238  // as per 5780, section 6.1, If the Request contained a PADDING attribute...
239  // "If the Request also contains the RESPONSE-PORT attribute the server MUST return an error response of type 400."
240  if (_fRequestHasResponsePort && fRequestHasPaddingAttribute)
241  {
243  return E_FAIL;
244  }
245 
246  // handle change request logic and figure out what "other-address" attribute is going to be
247  // Some clients (like jstun) will send a change-request attribute with neither the IP or PORT flag set
248  // So ignore this block of code in that case (because the fConnectionOriented check below could fail)
249  hrResult = reader.GetChangeRequest(&changerequest);
250  if (SUCCEEDED(hrResult) && (changerequest.fChangeIP || changerequest.fChangePort))
251  {
252  if (changerequest.fChangeIP)
253  {
254  socketOutput = SocketRoleSwapIP(socketOutput);
255  }
256  if(changerequest.fChangePort)
257  {
258  socketOutput = SocketRoleSwapPort(socketOutput);
259  }
260 
261  // IsValidSocketRole just validates the enum, not whether or not we can send on it
262  ASSERT(IsValidSocketRole(socketOutput));
263 
264  // now, make sure we have the ability to send from another socket
265  // For TCP/TLS, we can't send back from another port
266  if ((HasAddress(socketOutput) == false) || _pMsgIn->fConnectionOriented)
267  {
268  // send back an error. We're being asked to respond using another address that we don't have a socket for
270  return E_FAIL;
271  }
272  }
273 
274  // If we're only working one socket, then that's ok, we just don't send back an "other address" unless we have all four sockets configured
275  // now here's a problem. If we binded to "INADDR_ANY", all of the sockets will have "0.0.0.0" for an address (same for IPV6)
276  // So we effectively can't send back "other address" if don't really know our own IP address
277  // Fortunately, recvfromex and the ioctls on the socket allow address discovery a bit better
278 
279  // For TCP, we can send back an other-address. But it is only meant as as
280  // a hint to the client that he can try another server to infer NAT behavior
281  // Change-requests are disallowed
282 
283  // Note - As per RFC 5780 and RFC 3489, "other address" (aka "changed address")
284  // attribute is always the ip and port opposite of where the request was
285  // received on, irrespective of the client sending a change-requset that influenced
286  // the value of socketOutput value above.
287 
288  fSendOtherAddress = HasAddress(RolePP) && HasAddress(RolePA) && HasAddress(RoleAP) && HasAddress(RoleAA);
289 
290  if (fSendOtherAddress)
291  {
293  // so if our ip address is "0.0.0.0", disable this attribute
294  fSendOtherAddress = (IsIPAddressZeroOrInvalid(socketOther) == false);
295 
296  // so if the local address of the other socket isn't known (e.g. ip == "0.0.0.0"), disable this attribute
297  if (fSendOtherAddress)
298  {
299  addrOther = _pAddrSet->set[socketOther].addr;
300  }
301  }
302 
303  // What's our address origin?
304  addrOrigin = _pAddrSet->set[socketOutput].addr;
305  if (addrOrigin.IsIPAddressZero())
306  {
307  // Since we're sending back from the IP address we received on, we can just use the address the message came in on
308  // Otherwise, we don't actually know it
309  if (socketOutput == _pMsgIn->socketrole)
310  {
311  addrOrigin = _pMsgIn->addrLocal;
312  }
313  }
314  fSendOriginAddress = (false == addrOrigin.IsIPAddressZero());
315 
316  // Success - we're all clear to build the response
317  _pMsgOut->socketrole = socketOutput;
318 
319 
321  builder.AddTransactionId(_transid);
322 
323  // paranoia - just to be consistent with Vovida, send the attributes back in the same order it does
324  // I suspect there are clients out there that might be hardcoded to the ordering
325 
326  // MAPPED-ADDRESS
327  // SOURCE-ADDRESS (RESPONSE-ORIGIN)
328  // CHANGED-ADDRESS (OTHER-ADDRESS)
329  // XOR-MAPPED-ADDRESS (XOR-MAPPED_ADDRESS-OPTIONAL)
330 
332 
333  if (fSendOriginAddress)
334  {
335  builder.AddResponseOriginAddress(addrOrigin); // pass true to send back SOURCE_ADDRESS, otherwise, pass false to send back RESPONSE-ORIGIN
336  }
337 
338  if (fSendOtherAddress)
339  {
340  builder.AddOtherAddress(addrOther); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS
341  }
342 
343  // send back the XOR-MAPPED-ADDRESS (encoded as an optional message for legacy clients)
345 
346 
347  // finally - if we're supposed to have a message integrity attribute as a result of authorization, add it at the very end
349  {
350  if (_integrity.fUseLongTerm == false)
351  {
353  }
354  else
355  {
357  }
358  }
359 
360  builder.FixLengthField();
361 
362  return S_OK;
363 }
364 
365 
367 {
368  AuthAttributes authattributes;
369  AuthResponse authresponse;
370  HRESULT hr = S_OK;
371  HRESULT hrRet = S_OK;
372 
373  // aliases
374  CStunMessageReader& reader = *(_pMsgIn->pReader);
375 
376  if (_pAuth == NULL)
377  {
378  return S_OK; // nothing to do if there is no auth mechanism in place
379  }
380 
381  memset(&authattributes, '\0', sizeof(authattributes));
382  memset(&authresponse, '\0', sizeof(authresponse));
383 
384  reader.GetStringAttributeByType(STUN_ATTRIBUTE_USERNAME, authattributes.szUser, ARRAYSIZE(authattributes.szUser));
385  reader.GetStringAttributeByType(STUN_ATTRIBUTE_REALM, authattributes.szRealm, ARRAYSIZE(authattributes.szRealm));
386  reader.GetStringAttributeByType(STUN_ATTRIBUTE_NONCE, authattributes.szNonce, ARRAYSIZE(authattributes.szNonce));
388  authattributes.fMessageIntegrityPresent = reader.HasMessageIntegrityAttribute();
389 
390  Chk(_pAuth->DoAuthCheck(&authattributes, &authresponse));
391 
392  // enforce that everything is null terminated
393  authresponse.szNonce[ARRAYSIZE(authresponse.szNonce)-1] = 0;
394  authresponse.szRealm[ARRAYSIZE(authresponse.szRealm)-1] = 0;
395  authresponse.szPassword[ARRAYSIZE(authresponse.szPassword)-1] = 0;
396 
397  // now decide how to handle the auth
398  if (authresponse.responseType == StaleNonce)
399  {
401  }
402  else if (authresponse.responseType == Unauthorized)
403  {
405  }
406  else if (authresponse.responseType == Reject)
407  {
409  }
410  else if (authresponse.responseType == Allow)
411  {
412  // nothing to do!
413  }
414  else if (authresponse.responseType == AllowConditional)
415  {
416  // validate the message in // if either ValidateAuth or ProcessBindingRequest set an errorcode....
417 
418  if (authresponse.authCredMech == AuthCredLongTerm)
419  {
420  hrRet = reader.ValidateMessageIntegrityLong(authattributes.szUser, authattributes.szRealm, authresponse.szPassword);
421  }
422  else
423  {
424  hrRet = reader.ValidateMessageIntegrityShort(authresponse.szPassword);
425  }
426 
427  if (SUCCEEDED(hrRet))
428  {
431 
432  COMPILE_TIME_ASSERT(sizeof(_integrity.szPassword)==sizeof(authresponse.szPassword));
433 
434  strcpy(_integrity.szPassword, authresponse.szPassword);
435  strcpy(_integrity.szUser, authattributes.szUser);
436  strcpy(_integrity.szRealm, authattributes.szRealm);
437  }
438  else
439  {
440  // bad password - so now turn this thing into a 401
442  }
443  }
444 
446  {
447  strcpy(_error.szRealm, authresponse.szRealm);
448  strcpy(_error.szNonce, authresponse.szNonce);
449  }
450 
451 Cleanup:
452  return hr;
453 }
454 
456 {
457  return (_pAddrSet && ::IsValidSocketRole(role) && _pAddrSet->set[role].fValid);
458 }
459 
461 {
462  bool fValid = HasAddress(role) && (_pAddrSet->set[role].addr.IsIPAddressZero()==false);
463  return !fValid;
464 }
465 
const uint32_t MAX_STUN_MESSAGE_SIZE
Definition: stuntypes.h:178
TransportAddressSet * _pAddrSet
AuthResponseType responseType
Definition: stunauth.h:54
#define S_OK
Definition: hresult.h:46
const uint16_t STUN_ATTRIBUTE_USERNAME
Definition: stuntypes.h:39
#define ASSERT(expr)
bool IsValidSocketRole(SocketRole sr)
Definition: socketrole.h:31
char szUser[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:29
HRESULT AddUnknownAttributes(const uint16_t *arrAttributeIds, size_t count)
HRESULT GetChangeRequest(StunChangeRequestAttribute *pChangeRequest)
Definition: stunreader.cpp:407
HRESULT AddStringAttribute(uint16_t attribType, const char *pstr)
uint16_t GetMessageType()
Definition: stunreader.cpp:839
StunErrorCode _error
const uint16_t STUN_ERROR_STALENONCE
Definition: stuntypes.h:88
const uint16_t STUN_ERROR_UNAUTHORIZED
Definition: stuntypes.h:86
StunMessageIntegrity _integrity
HRESULT ValidateMessageIntegrityLong(const char *pszUser, const char *pszRealm, const char *pszPassword)
Definition: stunreader.cpp:282
SocketRole socketrole
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:31
#define Chk(expr)
Definition: chkmacros.h:53
HRESULT FixLengthField()
bool fMessageIntegrityPresent
Definition: stunauth.h:33
HRESULT AddXorMappedAddress(const CSocketAddress &addr)
SocketRole socketrole
void GetTransactionId(StunTransactionId *pTransId)
Definition: stunreader.cpp:825
CSocketAddress addr
const uint16_t STUN_ATTRIBUTE_REALM
Definition: stuntypes.h:51
AuthCredentialMechanism authCredMech
Definition: stunauth.h:55
#define ARRAYSIZE(arr)
const uint16_t STUN_ATTRIBUTE_NONCE
Definition: stuntypes.h:52
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]
#define ChkIf(expr, hrerror)
Definition: chkmacros.h:63
const uint16_t STUN_ERROR_BADREQUEST
Definition: stuntypes.h:85
uint16_t attribUnknown
bool IsMessageLegacyFormat()
Definition: stunreader.cpp:71
HRESULT AddMappedAddress(const CSocketAddress &addr)
#define E_UNEXPECTED
Definition: hresult.h:48
StunTransactionId _transid
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]
CSocketAddress addrRemote
What local IP address the message was received on (useful if the socket binded to INADDR_ANY) ...
#define SUCCEEDED(hr)
Definition: hresult.h:28
void SetLegacyMode(bool fLegacyMode)
Definition: stunbuilder.cpp:49
char szUser[MAX_STUN_AUTH_STRING_SIZE+1]
CSocketAddress addrLocal
which socket id did the message arrive on
uint16_t msgtype
char szPassword[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:57
void SetPort(uint16_t)
HRESULT AddHeader(StunMessageType msgType, StunMessageClass msgClass)
Definition: stunbuilder.cpp:55
HRESULT AddTransactionId(const StunTransactionId &transid)
Definition: stunbuilder.cpp:86
ReaderParseState GetState()
Definition: stunreader.cpp:820
HRESULT ProcessRequestImpl()
HRESULT AddResponseOriginAddress(const CSocketAddress &other)
HRESULT AddMessageIntegrityLongTerm(const char *pszUserName, const char *pszRealm, const char *pszPassword)
SocketRole
Definition: socketrole.h:22
HRESULT ValidateMessageIntegrityShort(const char *pszPassword)
Definition: stunreader.cpp:277
StunMessageClass msgclass
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:59
int32_t HRESULT
Definition: hresult.h:22
#define COMPILE_TIME_ASSERT(x)
SocketRole SocketRoleSwapPort(SocketRole sr)
Definition: socketrole.h:36
HRESULT GetResponsePort(uint16_t *pPort)
Definition: stunreader.cpp:384
HRESULT GetPaddingAttributeSize(uint16_t *pSizePadding)
Definition: stunreader.cpp:442
HRESULT ProcessBindingRequest()
const uint16_t STUN_ERROR_UNKNOWNATTRIB
Definition: stuntypes.h:87
Definition: stunauth.h:45
CSocketAddress addrDest
HRESULT GetResult(CRefCountedBuffer *pspBuffer)
HRESULT AddErrorCode(uint16_t errorNumber, const char *pszReason)
HRESULT AddMessageIntegrityShortTerm(const char *pszPassword)
char szPassword[MAX_STUN_AUTH_STRING_SIZE+1]
StunMessageType
Definition: stuntypes.h:102
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]
StunMessageOut * _pMsgOut
#define E_INVALIDARG
Definition: hresult.h:51
CStunMessageReader * pReader
the address of the node that sent us the message
#define E_FAIL
Definition: hresult.h:56
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:30
StunMessageClass GetMessageClass()
Definition: stunreader.cpp:834
#define FAILED(hr)
Definition: hresult.h:29
static HRESULT ProcessRequest(const StunMessageIn &msgIn, StunMessageOut &msgOut, TransportAddressSet *pAddressSet, IStunAuth *pAuth)
SocketRole SocketRoleSwapIP(SocketRole sr)
Definition: socketrole.h:41
bool HasMessageIntegrityAttribute()
Definition: stunreader.cpp:140
bool HasAddress(SocketRole role)
bool IsIPAddressZeroOrInvalid(SocketRole role)
TransportAddress set[4]
const uint16_t STUN_ATTRIBUTE_LEGACY_PASSWORD
Definition: stuntypes.h:41
CRefCountedBuffer spBufferOut
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:58
boost::shared_ptr< CBuffer > CRefCountedBuffer
Definition: buffer.h:65
HRESULT GetStringAttributeByType(uint16_t attributeType, char *pszValue, size_t size)
Definition: stunreader.cpp:565
const StunMessageIn * _pMsgIn
void Attach(CRefCountedBuffer &buffer, bool fForWriting)
Definition: datastream.cpp:55
bool IsIPAddressZero() const
virtual HRESULT DoAuthCheck(AuthAttributes *pAuthAttributes, AuthResponse *pResponse)=0
uint16_t errorcode
#define ChkIfA(expr, hrerror)
Definition: chkmacros.h:84
CDataStream & GetStream()
bool fConnectionOriented
reader containing a valid stun message
char szLegacyPassword[MAX_STUN_AUTH_STRING_SIZE+1]
Definition: stunauth.h:32
HRESULT AddOtherAddress(const CSocketAddress &other)