mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
expand tutorial on connection phases, thanks to Igor for pointing out that the text was incomplete
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
With the small exception of IP address based access control,
|
||||
With the small exception of IP address based access control,
|
||||
requests from all connecting clients where served equally until now.
|
||||
This chapter discusses a first method of client's authentication and
|
||||
its limits.
|
||||
its limits.
|
||||
|
||||
A very simple approach feasible with the means already discussed would
|
||||
be to expect the password in the @emph{URI} string before granting access to
|
||||
@@ -12,68 +12,102 @@ GET /picture.png?mypassword
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
In the rare situation where the client is customized enough and the connection occurs
|
||||
through secured lines (e.g., a embedded device directly attached to another via wire)
|
||||
and where the ability to embed a password in the URI or to pass on a URI with a
|
||||
password are desired, this can be a reasonable choice.
|
||||
In the rare situation where the client is customized enough and the connection
|
||||
occurs through secured lines (e.g., a embedded device directly attached to
|
||||
another via wire) and where the ability to embed a password in the URI or to
|
||||
pass on a URI with a password are desired, this can be a reasonable choice.
|
||||
|
||||
But when it is assumed that the user connecting does so with an ordinary Internet browser,
|
||||
this implementation brings some problems about. For example, the URI including the password
|
||||
stays in the address field or at least in the history of the browser for anybody near enough to see.
|
||||
It will also be inconvenient to add the password manually to any new URI when the browser does
|
||||
But when it is assumed that the user connecting does so with an ordinary
|
||||
Internet browser, this implementation brings some problems about. For example,
|
||||
the URI including the password stays in the address field or at least in the
|
||||
history of the browser for anybody near enough to see. It will also be
|
||||
inconvenient to add the password manually to any new URI when the browser does
|
||||
not know how to compose this automatically.
|
||||
|
||||
At least the convenience issue can be addressed by employing the simplest built-in password
|
||||
facilities of HTTP compliant browsers, hence we want to start there. It will however turn out
|
||||
to have still severe weaknesses in terms of security which need consideration.
|
||||
At least the convenience issue can be addressed by employing the simplest
|
||||
built-in password facilities of HTTP compliant browsers, hence we want to
|
||||
start there. It will, however, turn out to have still severe weaknesses in
|
||||
terms of security which need consideration.
|
||||
|
||||
Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617},
|
||||
we should finally abandon the bad practice of responding every request the first time our callback
|
||||
is called for a given connection. This is becoming more important now because the client and
|
||||
the server will have to talk in a more bi-directional way than before to
|
||||
Before we will start implementing @emph{Basic Authentication} as described in
|
||||
@emph{RFC 2617}, we will also abandon the simplistic and generally
|
||||
problematic practice of responding every request the first time our callback
|
||||
is called for a given connection. Queuing a response upon the first request
|
||||
is akin to generating an error response (even if it is a "200 OK" reply!).
|
||||
The reason is that MHD usually calls the callback in three phases:
|
||||
|
||||
But how can we tell whether the callback has been called before for the particular connection?
|
||||
Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will
|
||||
also be "remembered" on the next call (for the same connection).
|
||||
Thus, we will generate no response until the parameter is non-null---implying the callback was
|
||||
called before at least once. We do not need to share information between different calls of the callback,
|
||||
so we can set the parameter to any address that is assured to be not null. The pointer to the
|
||||
@code{connection} structure will be pointing to a legal address, so we take this.
|
||||
@enumerate
|
||||
@item
|
||||
First, to initially tell the application about the connection and inquire whether
|
||||
it is OK to proceed. This call typically happens before the client could upload
|
||||
the request body, and can be used to tell the client to not proceed with the
|
||||
upload (if the client requested "Expect: 100 Continue"). Applications may queue
|
||||
a reply at this point, but it will force the connection to be closed and thus
|
||||
prevent keep-alive / pipelining, which is generally a bad idea. Applications
|
||||
wanting to proceed with the request throughout the other phases should just return
|
||||
"MHD_YES" and not queue any response. Note that when an application suspends
|
||||
a connection in this callback, the phase does not advance and the application
|
||||
will be called again in this first phase.
|
||||
@item
|
||||
Next, to tell the application about upload data provided by the client.
|
||||
In this phase, the application may not queue replies, and trying to do so
|
||||
will result in MHD returning an error code from @code{MHD_queue_response}.
|
||||
If there is no upload data, this phase is skipped.
|
||||
@item
|
||||
Finally, to obtain a regular response from the application. This can be
|
||||
almost any type of response, including ones indicating failures. The
|
||||
one exception is a "100 Continue" response, which applications must never
|
||||
generate: MHD generates that response automatically when necessary in the
|
||||
first phase. If the application does not queue a response, MHD may call
|
||||
the callback repeatedly (depending a bit on the threading model, the
|
||||
application should suspend the connection).
|
||||
@end enumerate
|
||||
|
||||
But how can we tell whether the callback has been called before for the
|
||||
particular connection? Initially, the pointer this parameter references is
|
||||
set by @emph{MHD} in the callback. But it will also be "remembered" on the
|
||||
next call (for the same connection). Thus, we can use the @code{con_cls}
|
||||
location to keep track of the connection state. For now, we will simply
|
||||
generate no response until the parameter is non-null---implying the callback
|
||||
was called before at least once. We do not need to share information between
|
||||
different calls of the callback, so we can set the parameter to any address
|
||||
that is assured to be not null. The pointer to the @code{connection} structure
|
||||
will be pointing to a legal address, so we take this.
|
||||
|
||||
The first time @code{answer_to_connection} is called, we will not even look at the headers.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
static int
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url, const char *method, const char *version,
|
||||
const char *url, const char *method, const char *version,
|
||||
const char *upload_data, size_t *upload_data_size,
|
||||
void **con_cls)
|
||||
{
|
||||
if (0 != strcmp(method, "GET")) return MHD_NO;
|
||||
if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;}
|
||||
|
||||
...
|
||||
...
|
||||
/* else respond accordingly */
|
||||
...
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Note how we lop off the connection on the first condition (no "GET" request), but return asking for more on
|
||||
the other one with @code{MHD_YES}.
|
||||
With this minor change, we can proceed to implement the actual authentication process.
|
||||
Note how we lop off the connection on the first condition (no "GET" request),
|
||||
but return asking for more on the other one with @code{MHD_YES}. With this
|
||||
minor change, we can proceed to implement the actual authentication process.
|
||||
|
||||
@heading Request for authentication
|
||||
@heading Request for authentication
|
||||
|
||||
Let us assume we had only files not intended to be handed out without the correct username/password,
|
||||
so every "GET" request will be challenged.
|
||||
@emph{RFC 2617} describes how the server shall ask for authentication by adding a
|
||||
@emph{WWW-Authenticate} response header with the name of the @emph{realm} protected.
|
||||
MHD can generate and queue such a failure response for you using
|
||||
the @code{MHD_queue_basic_auth_fail_response} API. The only thing you need to do
|
||||
is construct a response with the error page to be shown to the user
|
||||
if he aborts basic authentication. But first, you should check if the
|
||||
proper credentials were already supplied using the
|
||||
Let us assume we had only files not intended to be handed out without the
|
||||
correct username/password, so every "GET" request will be challenged.
|
||||
@emph{RFC 2617} describes how the server shall ask for authentication by
|
||||
adding a @emph{WWW-Authenticate} response header with the name of the
|
||||
@emph{realm} protected. MHD can generate and queue such a failure response
|
||||
for you using the @code{MHD_queue_basic_auth_fail_response} API. The only
|
||||
thing you need to do is construct a response with the error page to be shown
|
||||
to the user if he aborts basic authentication. But first, you should check if
|
||||
the proper credentials were already supplied using the
|
||||
@code{MHD_basic_auth_get_username_password} call.
|
||||
|
||||
Your code would then look like this:
|
||||
@@ -101,14 +135,14 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
user = MHD_basic_auth_get_username_password (connection, &pass);
|
||||
fail = ( (user == NULL) ||
|
||||
(0 != strcmp (user, "root")) ||
|
||||
(0 != strcmp (pass, "pa$$w0rd") ) );
|
||||
(0 != strcmp (pass, "pa$$w0rd") ) );
|
||||
if (user != NULL) free (user);
|
||||
if (pass != NULL) free (pass);
|
||||
if (fail)
|
||||
{
|
||||
const char *page = "<html><body>Go away.</body></html>";
|
||||
response =
|
||||
MHD_create_response_from_buffer (strlen (page), (void *) page,
|
||||
MHD_create_response_from_buffer (strlen (page), (void *) page,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
ret = MHD_queue_basic_auth_fail_response (connection,
|
||||
"my realm",
|
||||
@@ -118,7 +152,7 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
{
|
||||
const char *page = "<html><body>A secret.</body></html>";
|
||||
response =
|
||||
MHD_create_response_from_buffer (strlen (page), (void *) page,
|
||||
MHD_create_response_from_buffer (strlen (page), (void *) page,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
||||
}
|
||||
@@ -130,9 +164,9 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
See the @code{examples} directory for the complete example file.
|
||||
|
||||
@heading Remarks
|
||||
For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a
|
||||
For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a
|
||||
response with a more precise status code instead of silently closing the connection. For example,
|
||||
failures of memory allocation are best reported as @emph{internal server error} and unexpected
|
||||
failures of memory allocation are best reported as @emph{internal server error} and unexpected
|
||||
authentication methods as @emph{400 bad request}.
|
||||
|
||||
@heading Exercises
|
||||
@@ -142,7 +176,7 @@ Make the server respond to wrong credentials (but otherwise well-formed requests
|
||||
@emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
|
||||
same connection, close it and store the client's IP address for a certain time. (It is OK to check for
|
||||
expiration not until the main thread wakes up again on the next connection.) If the client fails
|
||||
authenticating three times during this period, add it to another list for which the
|
||||
authenticating three times during this period, add it to another list for which the
|
||||
@code{AcceptPolicyCallback} function denies connection (temporally).
|
||||
|
||||
@item
|
||||
@@ -156,5 +190,3 @@ Copy and paste the encoded string you see in @code{netcat}'s output to some of t
|
||||
and see how both the user's name and password could be completely restored.
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ updated @value{UPDATED}.
|
||||
|
||||
Copyright (c) 2008 Sebastian Gerhardt.
|
||||
|
||||
Copyright (c) 2010, 2011, 2012, 2013, 2016 Christian Grothoff.
|
||||
Copyright (c) 2010, 2011, 2012, 2013, 2016, 2021 Christian Grothoff.
|
||||
@quotation
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
|
||||
Reference in New Issue
Block a user