Blame doc/chapters/basicauthentication.inc

Packit 875988
With the small exception of IP address based access control, 
Packit 875988
requests from all connecting clients where served equally until now.
Packit 875988
This chapter discusses a first method of client's authentication and
Packit 875988
its limits. 
Packit 875988
Packit 875988
A very simple approach feasible with the means already discussed would
Packit 875988
be to expect the password in the @emph{URI} string before granting access to
Packit 875988
the secured areas. The password could be separated from the actual resource identifier
Packit 875988
by a certain character, thus the request line might look like
Packit 875988
@verbatim
Packit 875988
GET /picture.png?mypassword
Packit 875988
@end verbatim
Packit 875988
@noindent
Packit 875988
Packit 875988
In the rare situation where the client is customized enough and the connection occurs
Packit 875988
through secured lines (e.g., a embedded device directly attached to another via wire)
Packit 875988
and where the ability to embedd a password in the URI or to pass on a URI with a
Packit 875988
password are desired, this can be a reasonable choice. 
Packit 875988
Packit 875988
But when it is assumed that the user connecting does so with an ordinary Internet browser,
Packit 875988
this implementation brings some problems about. For example, the URI including the password
Packit 875988
stays in the address field or at least in the history of the browser for anybody near enough to see. 
Packit 875988
It will also be inconvenient to add the password manually to any new URI when the browser does
Packit 875988
not know how to compose this automatically.
Packit 875988
Packit 875988
At least the convenience issue can be addressed by employing the simplest built-in password
Packit 875988
facilities of HTTP compliant browsers, hence we want to start there. It will however turn out
Packit 875988
to have still severe weaknesses in terms of security which need consideration.
Packit 875988
Packit 875988
Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617},
Packit 875988
we should finally abandon the bad practice of responding every request the first time our callback
Packit 875988
is called for a given connection. This is becoming more important now because the client and 
Packit 875988
the server will have to talk in a more bi-directional way than before to 
Packit 875988
Packit 875988
But how can we tell whether the callback has been called before for the particular connection?
Packit 875988
Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will 
Packit 875988
also be "remembered" on the next call (for the same connection).
Packit 875988
Thus, we will generate no response until the parameter is non-null---implying the callback was
Packit 875988
called before at least once. We do not need to share information between different calls of the callback,
Packit 875988
so we can set the parameter to any adress that is assured to be not null. The pointer to the 
Packit 875988
@code{connection} structure will be pointing to a legal address, so we take this.
Packit 875988
Packit 875988
The first time @code{answer_to_connection} is called, we will not even look at the headers.
Packit 875988
Packit 875988
@verbatim
Packit 875988
static int 
Packit 875988
answer_to_connection (void *cls, struct MHD_Connection *connection,
Packit 875988
                      const char *url, const char *method, const char *version, 
Packit 875988
                      const char *upload_data, size_t *upload_data_size,
Packit 875988
                      void **con_cls)
Packit 875988
{
Packit 875988
  if (0 != strcmp(method, "GET")) return MHD_NO;
Packit 875988
  if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;}
Packit 875988
Packit 875988
  ... 
Packit 875988
  /* else respond accordingly */
Packit 875988
  ...
Packit 875988
}
Packit 875988
@end verbatim
Packit 875988
@noindent
Packit 875988
Packit 875988
Note how we lop off the connection on the first condition (no "GET" request), but return asking for more on 
Packit 875988
the other one with @code{MHD_YES}.
Packit 875988
With this minor change, we can proceed to implement the actual authentication process.
Packit 875988
Packit 875988
@heading Request for authentication 
Packit 875988
Packit 875988
Let us assume we had only files not intended to be handed out without the correct username/password,
Packit 875988
so every "GET" request will be challenged.
Packit 875988
@emph{RFC 2617} describes how the server shall ask for authentication by adding a
Packit 875988
@emph{WWW-Authenticate} response header with the name of the @emph{realm} protected.
Packit 875988
MHD can generate and queue such a failure response for you using
Packit 875988
the @code{MHD_queue_basic_auth_fail_response} API.  The only thing you need to do
Packit 875988
is construct a response with the error page to be shown to the user
Packit 875988
if he aborts basic authentication.  But first, you should check if the
Packit 875988
proper credentials were already supplied using the
Packit 875988
@code{MHD_basic_auth_get_username_password} call.
Packit 875988
Packit 875988
Your code would then look like this:
Packit 875988
@verbatim
Packit 875988
static int
Packit 875988
answer_to_connection (void *cls, struct MHD_Connection *connection,
Packit 875988
                      const char *url, const char *method,
Packit 875988
                      const char *version, const char *upload_data,
Packit 875988
                      size_t *upload_data_size, void **con_cls)
Packit 875988
{
Packit 875988
  char *user;
Packit 875988
  char *pass;
Packit 875988
  int fail;
Packit 875988
  struct MHD_Response *response;
Packit 875988
Packit 875988
  if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
Packit 875988
    return MHD_NO;
Packit 875988
  if (NULL == *con_cls)
Packit 875988
    {
Packit 875988
      *con_cls = connection;
Packit 875988
      return MHD_YES;
Packit 875988
    }
Packit 875988
  pass = NULL;
Packit 875988
  user = MHD_basic_auth_get_username_password (connection, &pass);
Packit 875988
  fail = ( (user == NULL) ||
Packit 875988
	   (0 != strcmp (user, "root")) ||
Packit 875988
	   (0 != strcmp (pass, "pa$$w0rd") ) );  
Packit 875988
  if (user != NULL) free (user);
Packit 875988
  if (pass != NULL) free (pass);
Packit 875988
  if (fail)
Packit 875988
    {
Packit 875988
      const char *page = "<html><body>Go away.</body></html>";
Packit 875988
      response =
Packit 875988
	MHD_create_response_from_buffer (strlen (page), (void *) page, 
Packit 875988
				       MHD_RESPMEM_PERSISTENT);
Packit 875988
      ret = MHD_queue_basic_auth_fail_response (connection,
Packit 875988
						"my realm",
Packit 875988
						response);
Packit 875988
    }
Packit 875988
  else
Packit 875988
    {
Packit 875988
      const char *page = "<html><body>A secret.</body></html>";
Packit 875988
      response =
Packit 875988
	MHD_create_response_from_buffer (strlen (page), (void *) page, 
Packit 875988
				       MHD_RESPMEM_PERSISTENT);
Packit 875988
      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
Packit 875988
    }
Packit 875988
  MHD_destroy_response (response);
Packit 875988
  return ret;
Packit 875988
}
Packit 875988
@end verbatim
Packit 875988
Packit 875988
See the @code{examples} directory for the complete example file.
Packit 875988
Packit 875988
@heading Remarks
Packit 875988
For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a 
Packit 875988
response with a more precise status code instead of silently closing the connection. For example,
Packit 875988
failures of memory allocation are best reported as @emph{internal server error} and unexpected 
Packit 875988
authentication methods as @emph{400 bad request}.
Packit 875988
Packit 875988
@heading Exercises
Packit 875988
@itemize @bullet
Packit 875988
@item
Packit 875988
Make the server respond to wrong credentials (but otherwise well-formed requests) with the recommended
Packit 875988
@emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
Packit 875988
same connection, close it and store the client's IP address for a certain time. (It is OK to check for
Packit 875988
expiration not until the main thread wakes up again on the next connection.) If the client fails
Packit 875988
authenticating three times during this period, add it to another list for which the 
Packit 875988
@code{AcceptPolicyCallback} function denies connection (temporally).
Packit 875988
Packit 875988
@item
Packit 875988
With the network utility @code{netcat} connect and log the response of a "GET" request as you
Packit 875988
did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
Packit 875988
listen on the same port the server used to listen on and have it fake being the proper server by giving
Packit 875988
the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
Packit 875988
connecting to the actual server, browse to the eavesdropper and give the correct credentials.
Packit 875988
Packit 875988
Copy and paste the encoded string you see in @code{netcat}'s output to some of the Base64 decode tools available online
Packit 875988
and see how both the user's name and password could be completely restored.
Packit 875988
Packit 875988
@end itemize
Packit 875988
Packit 875988