|
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 |
|