mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
206 lines
9.9 KiB
HTML
206 lines
9.9 KiB
HTML
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.
|
|
|
|
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
|
|
the secured areas. The password could be separated from the actual resource identifier
|
|
by a certain character, thus the request line might look like
|
|
@verbatim
|
|
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.
|
|
|
|
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.
|
|
|
|
Before we will start implementing @emph{Basic Authentication} as described in
|
|
@emph{RFC 7617}, 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:
|
|
|
|
@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 request? This is what the @code{req_cls} parameter of the callback
|
|
is for: initially, the pointer it references is set to NULL by @emph{MHD}.
|
|
But whatever we store there will be "remembered" on the
|
|
next call (for the same request). Thus, we can use the @code{req_cls}
|
|
location to keep track of the request 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 enum MHD_Result
|
|
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
|
const char *url, const char *method, const char *version,
|
|
const char *upload_data, size_t *upload_data_size,
|
|
void **req_cls)
|
|
{
|
|
if (0 != strcmp(method, "GET")) return MHD_NO;
|
|
if (NULL == *req_cls) {*req_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.
|
|
|
|
@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 7617} 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_required_response3} 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_password3} call. (The older
|
|
@code{MHD_queue_basic_auth_fail_response} and
|
|
@code{MHD_basic_auth_get_username_password} calls are deprecated.)
|
|
|
|
Your code would then look like this:
|
|
@verbatim
|
|
static enum MHD_Result
|
|
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
|
const char *url, const char *method,
|
|
const char *version, const char *upload_data,
|
|
size_t *upload_data_size, void **req_cls)
|
|
{
|
|
struct MHD_BasicAuthInfo *auth_info;
|
|
enum MHD_Result ret;
|
|
struct MHD_Response *response;
|
|
|
|
if (0 != strcmp (method, "GET"))
|
|
return MHD_NO;
|
|
if (NULL == *req_cls)
|
|
{
|
|
*req_cls = connection;
|
|
return MHD_YES;
|
|
}
|
|
auth_info = MHD_basic_auth_get_username_password3 (connection);
|
|
if (NULL == auth_info)
|
|
{
|
|
static const char *page =
|
|
"<html><body>Authorization required</body></html>";
|
|
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
|
ret = MHD_queue_basic_auth_required_response3 (connection,
|
|
"admins",
|
|
MHD_YES,
|
|
response);
|
|
}
|
|
else if ((strlen ("root") != auth_info->username_len) ||
|
|
(0 != memcmp (auth_info->username, "root",
|
|
auth_info->username_len)) ||
|
|
/* The next check against NULL is optional,
|
|
* if 'password' is NULL then 'password_len' is always zero. */
|
|
(NULL == auth_info->password) ||
|
|
(strlen ("pa$$w0rd") != auth_info->password_len) ||
|
|
(0 != memcmp (auth_info->password, "pa$$w0rd",
|
|
auth_info->password_len)))
|
|
{
|
|
static const char *page =
|
|
"<html><body>Wrong username or password</body></html>";
|
|
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
|
ret = MHD_queue_basic_auth_required_response3 (connection,
|
|
"admins",
|
|
MHD_YES,
|
|
response);
|
|
}
|
|
else
|
|
{
|
|
static const char *page = "<html><body>A secret.</body></html>";
|
|
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
|
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
|
}
|
|
if (NULL != auth_info)
|
|
MHD_free (auth_info);
|
|
MHD_destroy_response (response);
|
|
return ret;
|
|
}
|
|
@end verbatim
|
|
|
|
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
|
|
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
|
|
authentication methods as @emph{400 bad request}.
|
|
|
|
@heading Exercises
|
|
@itemize @bullet
|
|
@item
|
|
Make the server respond to wrong credentials (but otherwise well-formed requests) with the recommended
|
|
@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
|
|
@code{AcceptPolicyCallback} function denies connection (temporally).
|
|
|
|
@item
|
|
With the network utility @code{netcat} connect and log the response of a "GET" request as you
|
|
did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
|
|
listen on the same port the server used to listen on and have it fake being the proper server by giving
|
|
the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
|
|
connecting to the actual server, browse to the eavesdropper and give the correct credentials.
|
|
|
|
Copy and paste the encoded string you see in @code{netcat}'s output to some of the Base64 decode tools available online
|
|
and see how both the user's name and password could be completely restored.
|
|
|
|
@end itemize
|