diff options
Diffstat (limited to 'doc/chapters/basicauthentication.inc')
-rw-r--r-- | doc/chapters/basicauthentication.inc | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/doc/chapters/basicauthentication.inc b/doc/chapters/basicauthentication.inc new file mode 100644 index 00000000..8e7f4c60 --- /dev/null +++ b/doc/chapters/basicauthentication.inc | |||
@@ -0,0 +1,207 @@ | |||
1 | With the small exception of IP address based access control, | ||
2 | requests from all connecting clients where served equally until now. | ||
3 | This chapter discusses a first method of client's authentication and | ||
4 | its limits. | ||
5 | |||
6 | A very simple approach feasible with the means already discussed would | ||
7 | be to expect the password in the @emph{URI} string before granting access to | ||
8 | the secured areas. The password could be separated from the actual resource identifier | ||
9 | by a certain character, thus the request line might look like | ||
10 | @verbatim | ||
11 | GET /picture.png?mypassword | ||
12 | @end verbatim | ||
13 | @noindent | ||
14 | |||
15 | In a situation, where the client is customized enough and the connection occurs | ||
16 | through secured lines (e.g., a embedded device directly attached to another via wire), | ||
17 | this can be a reasonable choice. | ||
18 | |||
19 | But when it is assumed that the user connecting does so with an ordinary Internet browser, | ||
20 | this implementation brings some problems about. For example, the URI including the password | ||
21 | stays in the address field or at least in the history of the browser for anybody near enough to see. | ||
22 | It will also be inconvenient to add the password manually to any new URI when the browser does | ||
23 | not know how to compose this automatically. | ||
24 | |||
25 | At least the convenience issue can be addressed by employing the simplest built-in password | ||
26 | facilities of HTTP compliant browsers, hence we want to start there. It will however turn out | ||
27 | to have still severe weaknesses in terms of security which need consideration. | ||
28 | |||
29 | Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617}, | ||
30 | we should finally abandon the bad practice of responding every request the first time our callback | ||
31 | is called for a given connection. This is becoming more important now because the client and | ||
32 | the server will have to talk in a more bi-directional way than before to | ||
33 | |||
34 | But how can we tell whether the callback has been called before for the particular connection? | ||
35 | Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will | ||
36 | also be "remembered" on the next call (for the same connection). | ||
37 | Thus, we will generate no response until the parameter is non-null---implying the callback was | ||
38 | called before at least once. We do not need to share information between different calls of the callback, | ||
39 | so we can set the parameter to any adress that is assured to be not null. The pointer to the | ||
40 | @code{connection} structure will be pointing to a legal adress, so we take this. | ||
41 | |||
42 | Not even the headers will be looked at on the first iteration. | ||
43 | |||
44 | @verbatim | ||
45 | int answer_to_connection (void *cls, struct MHD_Connection *connection, | ||
46 | const char *url, const char *method, const char *version, | ||
47 | const char *upload_data, unsigned int *upload_data_size, | ||
48 | void **con_cls) | ||
49 | { | ||
50 | if (0 != strcmp(method, "GET")) return MHD_NO; | ||
51 | if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;} | ||
52 | |||
53 | ... | ||
54 | /* else respond accordingly */ | ||
55 | ... | ||
56 | } | ||
57 | @end verbatim | ||
58 | @noindent | ||
59 | |||
60 | Note how we lop off the connection on the first condition, but return asking for more on | ||
61 | the other one with @code{MHD_YES}. | ||
62 | With the framework improved, we can proceed to implement the actual authentication process. | ||
63 | |||
64 | @heading Request for authentication | ||
65 | |||
66 | Let us assume we had only files not intended to be handed out without the correct username/password, | ||
67 | so every "GET" request will be challenged. | ||
68 | @emph{RFC 2617} describes how the server shall ask for authentication by adding a | ||
69 | @emph{WWW-Authenticate} response header with the name of the @emph{realm} protected. | ||
70 | |||
71 | We let an extra function function do this. | ||
72 | @verbatim | ||
73 | int ask_for_authentication (struct MHD_Connection *connection, const char *realm) | ||
74 | { | ||
75 | int ret; | ||
76 | struct MHD_Response *response; | ||
77 | char *headervalue; | ||
78 | const char *strbase = "Basic realm="; | ||
79 | |||
80 | response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO); | ||
81 | if (!response) return MHD_NO; | ||
82 | |||
83 | headervalue = malloc (strlen (strbase) + strlen (realm) + 1); | ||
84 | if (!headervalue) return MHD_NO; | ||
85 | |||
86 | strcpy (headervalue, strbase); | ||
87 | strcat (headervalue, realm); | ||
88 | |||
89 | ret = MHD_add_response_header (response, "WWW-Authenticate", headervalue); | ||
90 | free (headervalue); | ||
91 | if (!ret) {MHD_destroy_response (response); return MHD_NO;} | ||
92 | |||
93 | ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response); | ||
94 | |||
95 | MHD_destroy_response (response); | ||
96 | |||
97 | return ret; | ||
98 | } | ||
99 | @end verbatim | ||
100 | @noindent | ||
101 | |||
102 | @code{#define} the realm name according to your own taste, e.g. "Maintenance" or "Area51" but | ||
103 | it will need to have extra quotes. | ||
104 | |||
105 | But the client may send the authentication right away, so it would be wrong to ask for | ||
106 | it without checking the request's header--where the authentication is expected to be found. | ||
107 | |||
108 | @heading Authentication in detail | ||
109 | Checking @emph{RFC 2617} again, we find that the client will pack the username and password, by | ||
110 | whatever means he might have obtained them, in a line separated by a colon---and then encodes | ||
111 | them to @emph{Base64}. The actual implementation of this encoding are not within the scope of | ||
112 | this tutorial although a working function is included in the complete source file of the example. | ||
113 | |||
114 | An unencoded word describing the authentication method (here "Basic") will precede the code | ||
115 | and the resulting line is the value of a request header of the type "Authorization". | ||
116 | |||
117 | This header line thus is of interest to the function checking a connection for a given username/password: | ||
118 | @verbatim | ||
119 | int is_authenticated (struct MHD_Connection *connection, | ||
120 | const char *username, const char *password) | ||
121 | { | ||
122 | const char *headervalue; | ||
123 | ... | ||
124 | |||
125 | headervalue = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, | ||
126 | "Authorization"); | ||
127 | if (NULL == headervalue) return 0; | ||
128 | @end verbatim | ||
129 | @noindent | ||
130 | |||
131 | where, firstly, the authentication method will be checked. | ||
132 | @verbatim | ||
133 | const char *strbase = "Basic "; | ||
134 | ... | ||
135 | if (0 != strncmp (headervalue, strbase, strlen (strbase))) return 0; | ||
136 | @end verbatim | ||
137 | @noindent | ||
138 | |||
139 | Of course, we could decode the passed credentials in the next step and compare them directly to | ||
140 | the given strings. But as this would involve string parsing, which is more complicated then string | ||
141 | composing, it is done the other way around---the clear text credentials will be encoded to @emph{Base64} | ||
142 | and then compared against the headerline. The authentication method string will be left out here as | ||
143 | it has been checked already at this point. | ||
144 | @verbatim | ||
145 | char *expected_b64, *expected; | ||
146 | int authenticated; | ||
147 | |||
148 | ... | ||
149 | strcpy (expected, username); | ||
150 | strcat (expected, ":"); | ||
151 | strcat (expected, password); | ||
152 | |||
153 | expected_b64 = string_to_base64 (expected); | ||
154 | if (NULL == expected_b64) return 0; | ||
155 | |||
156 | strcpy (expected, strbase); | ||
157 | authenticated = (strcmp (headervalue + strlen (strbase), expected_b64) == 0); | ||
158 | |||
159 | free (expected_b64); | ||
160 | |||
161 | return authenticated; | ||
162 | } | ||
163 | @end verbatim | ||
164 | @noindent | ||
165 | |||
166 | These two functions---together with a response function in case of positive authentication doing little | ||
167 | new---allow the rest of the callback function to be rather short. | ||
168 | @verbatim | ||
169 | if (!is_authenticated (connection, USER, PASSWORD)) | ||
170 | return ask_for_authentication (connection, REALM); | ||
171 | |||
172 | return secret_page (connection); | ||
173 | } | ||
174 | @end verbatim | ||
175 | @noindent | ||
176 | |||
177 | See the @code{examples} directory for the complete example file. | ||
178 | |||
179 | @heading Remarks | ||
180 | For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a | ||
181 | response with a more precise status code instead of silently closing the connection. For example, | ||
182 | failures of memory allocation are best reported as @emph{internal server error} and unexpected | ||
183 | authentication methods as @emph{400 bad request}. | ||
184 | |||
185 | @heading Exercises | ||
186 | @itemize @bullet | ||
187 | @item | ||
188 | Make the server respond to wrong credentials (but else correct requests) with the recommended | ||
189 | @emph{401 unauthorized} status code. If the client still does not authenticate correctly within the | ||
190 | same connection, close it and store the client's IP address for a certain time. (It is OK to check for | ||
191 | expiration not until the main thread wakes up again on the next connection.) If the client fails | ||
192 | authenticating three times during this period, add it to another list whose entries the | ||
193 | @code{AcceptPolicyCallback} function denies connection (temporally). | ||
194 | |||
195 | @item | ||
196 | With the network utility @emph{netcat} connect and log the response of a "GET" request as you | ||
197 | did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat} | ||
198 | listen on the same port the server used to listen on and have it fake being the proper server by giving | ||
199 | the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were | ||
200 | connecting to the actual server, browse to the eavesdropper and give the correct credentials. | ||
201 | |||
202 | Copy and paste the encoded string you see in netcat's output to some of the Base64 decode tools available online | ||
203 | and see how both the user's name and password could be completely restored. | ||
204 | |||
205 | @end itemize | ||
206 | |||
207 | |||