• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1With the small exception of IP address based access control,
2requests from all connecting clients where served equally until now.
3This chapter discusses a first method of client's authentication and
4its limits.
5
6A very simple approach feasible with the means already discussed would
7be to expect the password in the @emph{URI} string before granting access to
8the secured areas. The password could be separated from the actual resource identifier
9by a certain character, thus the request line might look like
10@verbatim
11GET /picture.png?mypassword
12@end verbatim
13@noindent
14
15In the rare situation where the client is customized enough and the connection occurs
16through secured lines (e.g., a embedded device directly attached to another via wire)
17and where the ability to embedd a password in the URI or to pass on a URI with a
18password are desired, this can be a reasonable choice.
19
20But when it is assumed that the user connecting does so with an ordinary Internet browser,
21this implementation brings some problems about. For example, the URI including the password
22stays in the address field or at least in the history of the browser for anybody near enough to see.
23It will also be inconvenient to add the password manually to any new URI when the browser does
24not know how to compose this automatically.
25
26At least the convenience issue can be addressed by employing the simplest built-in password
27facilities of HTTP compliant browsers, hence we want to start there. It will however turn out
28to have still severe weaknesses in terms of security which need consideration.
29
30Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617},
31we should finally abandon the bad practice of responding every request the first time our callback
32is called for a given connection. This is becoming more important now because the client and
33the server will have to talk in a more bi-directional way than before to
34
35But how can we tell whether the callback has been called before for the particular connection?
36Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will
37also be "remembered" on the next call (for the same connection).
38Thus, we will generate no response until the parameter is non-null---implying the callback was
39called before at least once. We do not need to share information between different calls of the callback,
40so we can set the parameter to any adress that is assured to be not null. The pointer to the
41@code{connection} structure will be pointing to a legal address, so we take this.
42
43The first time @code{answer_to_connection} is called, we will not even look at the headers.
44
45@verbatim
46static int
47answer_to_connection (void *cls, struct MHD_Connection *connection,
48                      const char *url, const char *method, const char *version,
49                      const char *upload_data, size_t *upload_data_size,
50                      void **con_cls)
51{
52  if (0 != strcmp(method, "GET")) return MHD_NO;
53  if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;}
54
55  ...
56  /* else respond accordingly */
57  ...
58}
59@end verbatim
60@noindent
61
62Note how we lop off the connection on the first condition (no "GET" request), but return asking for more on
63the other one with @code{MHD_YES}.
64With this minor change, we can proceed to implement the actual authentication process.
65
66@heading Request for authentication
67
68Let us assume we had only files not intended to be handed out without the correct username/password,
69so every "GET" request will be challenged.
70@emph{RFC 2617} describes how the server shall ask for authentication by adding a
71@emph{WWW-Authenticate} response header with the name of the @emph{realm} protected.
72MHD can generate and queue such a failure response for you using
73the @code{MHD_queue_basic_auth_fail_response} API.  The only thing you need to do
74is construct a response with the error page to be shown to the user
75if he aborts basic authentication.  But first, you should check if the
76proper credentials were already supplied using the
77@code{MHD_basic_auth_get_username_password} call.
78
79Your code would then look like this:
80@verbatim
81static int
82answer_to_connection (void *cls, struct MHD_Connection *connection,
83                      const char *url, const char *method,
84                      const char *version, const char *upload_data,
85                      size_t *upload_data_size, void **con_cls)
86{
87  char *user;
88  char *pass;
89  int fail;
90  struct MHD_Response *response;
91
92  if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
93    return MHD_NO;
94  if (NULL == *con_cls)
95    {
96      *con_cls = connection;
97      return MHD_YES;
98    }
99  pass = NULL;
100  user = MHD_basic_auth_get_username_password (connection, &pass);
101  fail = ( (user == NULL) ||
102	   (0 != strcmp (user, "root")) ||
103	   (0 != strcmp (pass, "pa$$w0rd") ) );
104  if (user != NULL) free (user);
105  if (pass != NULL) free (pass);
106  if (fail)
107    {
108      const char *page = "<html><body>Go away.</body></html>";
109      response =
110	MHD_create_response_from_buffer (strlen (page), (void *) page,
111				       MHD_RESPMEM_PERSISTENT);
112      ret = MHD_queue_basic_auth_fail_response (connection,
113						"my realm",
114						response);
115    }
116  else
117    {
118      const char *page = "<html><body>A secret.</body></html>";
119      response =
120	MHD_create_response_from_buffer (strlen (page), (void *) page,
121				       MHD_RESPMEM_PERSISTENT);
122      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
123    }
124  MHD_destroy_response (response);
125  return ret;
126}
127@end verbatim
128
129See the @code{examples} directory for the complete example file.
130
131@heading Remarks
132For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a
133response with a more precise status code instead of silently closing the connection. For example,
134failures of memory allocation are best reported as @emph{internal server error} and unexpected
135authentication methods as @emph{400 bad request}.
136
137@heading Exercises
138@itemize @bullet
139@item
140Make the server respond to wrong credentials (but otherwise well-formed requests) with the recommended
141@emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
142same connection, close it and store the client's IP address for a certain time. (It is OK to check for
143expiration not until the main thread wakes up again on the next connection.) If the client fails
144authenticating three times during this period, add it to another list for which the
145@code{AcceptPolicyCallback} function denies connection (temporally).
146
147@item
148With the network utility @code{netcat} connect and log the response of a "GET" request as you
149did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
150listen on the same port the server used to listen on and have it fake being the proper server by giving
151the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
152connecting to the actual server, browse to the eavesdropper and give the correct credentials.
153
154Copy and paste the encoded string you see in @code{netcat}'s output to some of the Base64 decode tools available online
155and see how both the user's name and password could be completely restored.
156
157@end itemize
158
159
160