diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-02-02 18:40:10 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-02-02 18:40:10 +0100 |
commit | e06693862812597b14b4eccd2c06f71dff6ca846 (patch) | |
tree | a90ce7bb4f4e4eb703cc5ea589205f7fcb0eafc9 | |
parent | 0394bc7213794b463e7edc110cd3ce2ab3ee261b (diff) | |
download | libmicrohttpd-e06693862812597b14b4eccd2c06f71dff6ca846.tar.gz libmicrohttpd-e06693862812597b14b4eccd2c06f71dff6ca846.zip |
expand tutorial on connection phases, thanks to Igor for pointing out that the text was incomplete
-rw-r--r-- | doc/chapters/basicauthentication.inc | 132 | ||||
-rw-r--r-- | doc/libmicrohttpd-tutorial.texi | 2 |
2 files changed, 83 insertions, 51 deletions
diff --git a/doc/chapters/basicauthentication.inc b/doc/chapters/basicauthentication.inc index ec606f3e..ca48403a 100644 --- a/doc/chapters/basicauthentication.inc +++ b/doc/chapters/basicauthentication.inc | |||
@@ -1,7 +1,7 @@ | |||
1 | With the small exception of IP address based access control, | 1 | With the small exception of IP address based access control, |
2 | requests from all connecting clients where served equally until now. | 2 | requests from all connecting clients where served equally until now. |
3 | This chapter discusses a first method of client's authentication and | 3 | This chapter discusses a first method of client's authentication and |
4 | its limits. | 4 | its limits. |
5 | 5 | ||
6 | A very simple approach feasible with the means already discussed would | 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 | 7 | be to expect the password in the @emph{URI} string before granting access to |
@@ -12,68 +12,102 @@ GET /picture.png?mypassword | |||
12 | @end verbatim | 12 | @end verbatim |
13 | @noindent | 13 | @noindent |
14 | 14 | ||
15 | In the rare situation where the client is customized enough and the connection occurs | 15 | In the rare situation where the client is customized enough and the connection |
16 | through secured lines (e.g., a embedded device directly attached to another via wire) | 16 | occurs through secured lines (e.g., a embedded device directly attached to |
17 | and where the ability to embed a password in the URI or to pass on a URI with a | 17 | another via wire) and where the ability to embed a password in the URI or to |
18 | password are desired, this can be a reasonable choice. | 18 | pass on a URI with a password are desired, this can be a reasonable choice. |
19 | 19 | ||
20 | But when it is assumed that the user connecting does so with an ordinary Internet browser, | 20 | But when it is assumed that the user connecting does so with an ordinary |
21 | this implementation brings some problems about. For example, the URI including the password | 21 | Internet browser, this implementation brings some problems about. For example, |
22 | stays in the address field or at least in the history of the browser for anybody near enough to see. | 22 | the URI including the password stays in the address field or at least in the |
23 | It will also be inconvenient to add the password manually to any new URI when the browser does | 23 | history of the browser for anybody near enough to see. It will also be |
24 | inconvenient to add the password manually to any new URI when the browser does | ||
24 | not know how to compose this automatically. | 25 | not know how to compose this automatically. |
25 | 26 | ||
26 | At least the convenience issue can be addressed by employing the simplest built-in password | 27 | At least the convenience issue can be addressed by employing the simplest |
27 | facilities of HTTP compliant browsers, hence we want to start there. It will however turn out | 28 | built-in password facilities of HTTP compliant browsers, hence we want to |
28 | to have still severe weaknesses in terms of security which need consideration. | 29 | start there. It will, however, turn out to have still severe weaknesses in |
30 | terms of security which need consideration. | ||
29 | 31 | ||
30 | Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617}, | 32 | Before we will start implementing @emph{Basic Authentication} as described in |
31 | we should finally abandon the bad practice of responding every request the first time our callback | 33 | @emph{RFC 2617}, we will also abandon the simplistic and generally |
32 | is called for a given connection. This is becoming more important now because the client and | 34 | problematic practice of responding every request the first time our callback |
33 | the server will have to talk in a more bi-directional way than before to | 35 | is called for a given connection. Queuing a response upon the first request |
36 | is akin to generating an error response (even if it is a "200 OK" reply!). | ||
37 | The reason is that MHD usually calls the callback in three phases: | ||
34 | 38 | ||
35 | But how can we tell whether the callback has been called before for the particular connection? | 39 | @enumerate |
36 | Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will | 40 | @item |
37 | also be "remembered" on the next call (for the same connection). | 41 | First, to initially tell the application about the connection and inquire whether |
38 | Thus, we will generate no response until the parameter is non-null---implying the callback was | 42 | it is OK to proceed. This call typically happens before the client could upload |
39 | called before at least once. We do not need to share information between different calls of the callback, | 43 | the request body, and can be used to tell the client to not proceed with the |
40 | so we can set the parameter to any address that is assured to be not null. The pointer to the | 44 | upload (if the client requested "Expect: 100 Continue"). Applications may queue |
41 | @code{connection} structure will be pointing to a legal address, so we take this. | 45 | a reply at this point, but it will force the connection to be closed and thus |
46 | prevent keep-alive / pipelining, which is generally a bad idea. Applications | ||
47 | wanting to proceed with the request throughout the other phases should just return | ||
48 | "MHD_YES" and not queue any response. Note that when an application suspends | ||
49 | a connection in this callback, the phase does not advance and the application | ||
50 | will be called again in this first phase. | ||
51 | @item | ||
52 | Next, to tell the application about upload data provided by the client. | ||
53 | In this phase, the application may not queue replies, and trying to do so | ||
54 | will result in MHD returning an error code from @code{MHD_queue_response}. | ||
55 | If there is no upload data, this phase is skipped. | ||
56 | @item | ||
57 | Finally, to obtain a regular response from the application. This can be | ||
58 | almost any type of response, including ones indicating failures. The | ||
59 | one exception is a "100 Continue" response, which applications must never | ||
60 | generate: MHD generates that response automatically when necessary in the | ||
61 | first phase. If the application does not queue a response, MHD may call | ||
62 | the callback repeatedly (depending a bit on the threading model, the | ||
63 | application should suspend the connection). | ||
64 | @end enumerate | ||
65 | |||
66 | But how can we tell whether the callback has been called before for the | ||
67 | particular connection? Initially, the pointer this parameter references is | ||
68 | set by @emph{MHD} in the callback. But it will also be "remembered" on the | ||
69 | next call (for the same connection). Thus, we can use the @code{con_cls} | ||
70 | location to keep track of the connection state. For now, we will simply | ||
71 | generate no response until the parameter is non-null---implying the callback | ||
72 | was called before at least once. We do not need to share information between | ||
73 | different calls of the callback, so we can set the parameter to any address | ||
74 | that is assured to be not null. The pointer to the @code{connection} structure | ||
75 | will be pointing to a legal address, so we take this. | ||
42 | 76 | ||
43 | The first time @code{answer_to_connection} is called, we will not even look at the headers. | 77 | The first time @code{answer_to_connection} is called, we will not even look at the headers. |
44 | 78 | ||
45 | @verbatim | 79 | @verbatim |
46 | static int | 80 | static int |
47 | answer_to_connection (void *cls, struct MHD_Connection *connection, | 81 | answer_to_connection (void *cls, struct MHD_Connection *connection, |
48 | const char *url, const char *method, const char *version, | 82 | const char *url, const char *method, const char *version, |
49 | const char *upload_data, size_t *upload_data_size, | 83 | const char *upload_data, size_t *upload_data_size, |
50 | void **con_cls) | 84 | void **con_cls) |
51 | { | 85 | { |
52 | if (0 != strcmp(method, "GET")) return MHD_NO; | 86 | if (0 != strcmp(method, "GET")) return MHD_NO; |
53 | if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;} | 87 | if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;} |
54 | 88 | ||
55 | ... | 89 | ... |
56 | /* else respond accordingly */ | 90 | /* else respond accordingly */ |
57 | ... | 91 | ... |
58 | } | 92 | } |
59 | @end verbatim | 93 | @end verbatim |
60 | @noindent | 94 | @noindent |
61 | 95 | ||
62 | Note how we lop off the connection on the first condition (no "GET" request), but return asking for more on | 96 | Note how we lop off the connection on the first condition (no "GET" request), |
63 | the other one with @code{MHD_YES}. | 97 | but return asking for more on the other one with @code{MHD_YES}. With this |
64 | With this minor change, we can proceed to implement the actual authentication process. | 98 | minor change, we can proceed to implement the actual authentication process. |
65 | 99 | ||
66 | @heading Request for authentication | 100 | @heading Request for authentication |
67 | 101 | ||
68 | Let us assume we had only files not intended to be handed out without the correct username/password, | 102 | Let us assume we had only files not intended to be handed out without the |
69 | so every "GET" request will be challenged. | 103 | correct username/password, so every "GET" request will be challenged. |
70 | @emph{RFC 2617} describes how the server shall ask for authentication by adding a | 104 | @emph{RFC 2617} describes how the server shall ask for authentication by |
71 | @emph{WWW-Authenticate} response header with the name of the @emph{realm} protected. | 105 | adding a @emph{WWW-Authenticate} response header with the name of the |
72 | MHD can generate and queue such a failure response for you using | 106 | @emph{realm} protected. MHD can generate and queue such a failure response |
73 | the @code{MHD_queue_basic_auth_fail_response} API. The only thing you need to do | 107 | for you using the @code{MHD_queue_basic_auth_fail_response} API. The only |
74 | is construct a response with the error page to be shown to the user | 108 | thing you need to do is construct a response with the error page to be shown |
75 | if he aborts basic authentication. But first, you should check if the | 109 | to the user if he aborts basic authentication. But first, you should check if |
76 | proper credentials were already supplied using the | 110 | the proper credentials were already supplied using the |
77 | @code{MHD_basic_auth_get_username_password} call. | 111 | @code{MHD_basic_auth_get_username_password} call. |
78 | 112 | ||
79 | Your code would then look like this: | 113 | Your code would then look like this: |
@@ -101,14 +135,14 @@ answer_to_connection (void *cls, struct MHD_Connection *connection, | |||
101 | user = MHD_basic_auth_get_username_password (connection, &pass); | 135 | user = MHD_basic_auth_get_username_password (connection, &pass); |
102 | fail = ( (user == NULL) || | 136 | fail = ( (user == NULL) || |
103 | (0 != strcmp (user, "root")) || | 137 | (0 != strcmp (user, "root")) || |
104 | (0 != strcmp (pass, "pa$$w0rd") ) ); | 138 | (0 != strcmp (pass, "pa$$w0rd") ) ); |
105 | if (user != NULL) free (user); | 139 | if (user != NULL) free (user); |
106 | if (pass != NULL) free (pass); | 140 | if (pass != NULL) free (pass); |
107 | if (fail) | 141 | if (fail) |
108 | { | 142 | { |
109 | const char *page = "<html><body>Go away.</body></html>"; | 143 | const char *page = "<html><body>Go away.</body></html>"; |
110 | response = | 144 | response = |
111 | MHD_create_response_from_buffer (strlen (page), (void *) page, | 145 | MHD_create_response_from_buffer (strlen (page), (void *) page, |
112 | MHD_RESPMEM_PERSISTENT); | 146 | MHD_RESPMEM_PERSISTENT); |
113 | ret = MHD_queue_basic_auth_fail_response (connection, | 147 | ret = MHD_queue_basic_auth_fail_response (connection, |
114 | "my realm", | 148 | "my realm", |
@@ -118,7 +152,7 @@ answer_to_connection (void *cls, struct MHD_Connection *connection, | |||
118 | { | 152 | { |
119 | const char *page = "<html><body>A secret.</body></html>"; | 153 | const char *page = "<html><body>A secret.</body></html>"; |
120 | response = | 154 | response = |
121 | MHD_create_response_from_buffer (strlen (page), (void *) page, | 155 | MHD_create_response_from_buffer (strlen (page), (void *) page, |
122 | MHD_RESPMEM_PERSISTENT); | 156 | MHD_RESPMEM_PERSISTENT); |
123 | ret = MHD_queue_response (connection, MHD_HTTP_OK, response); | 157 | ret = MHD_queue_response (connection, MHD_HTTP_OK, response); |
124 | } | 158 | } |
@@ -130,9 +164,9 @@ answer_to_connection (void *cls, struct MHD_Connection *connection, | |||
130 | See the @code{examples} directory for the complete example file. | 164 | See the @code{examples} directory for the complete example file. |
131 | 165 | ||
132 | @heading Remarks | 166 | @heading Remarks |
133 | For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a | 167 | For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a |
134 | response with a more precise status code instead of silently closing the connection. For example, | 168 | response with a more precise status code instead of silently closing the connection. For example, |
135 | failures of memory allocation are best reported as @emph{internal server error} and unexpected | 169 | failures of memory allocation are best reported as @emph{internal server error} and unexpected |
136 | authentication methods as @emph{400 bad request}. | 170 | authentication methods as @emph{400 bad request}. |
137 | 171 | ||
138 | @heading Exercises | 172 | @heading Exercises |
@@ -142,7 +176,7 @@ Make the server respond to wrong credentials (but otherwise well-formed requests | |||
142 | @emph{401 unauthorized} status code. If the client still does not authenticate correctly within the | 176 | @emph{401 unauthorized} status code. If the client still does not authenticate correctly within the |
143 | same connection, close it and store the client's IP address for a certain time. (It is OK to check for | 177 | same connection, close it and store the client's IP address for a certain time. (It is OK to check for |
144 | expiration not until the main thread wakes up again on the next connection.) If the client fails | 178 | expiration not until the main thread wakes up again on the next connection.) If the client fails |
145 | authenticating three times during this period, add it to another list for which the | 179 | authenticating three times during this period, add it to another list for which the |
146 | @code{AcceptPolicyCallback} function denies connection (temporally). | 180 | @code{AcceptPolicyCallback} function denies connection (temporally). |
147 | 181 | ||
148 | @item | 182 | @item |
@@ -156,5 +190,3 @@ Copy and paste the encoded string you see in @code{netcat}'s output to some of t | |||
156 | and see how both the user's name and password could be completely restored. | 190 | and see how both the user's name and password could be completely restored. |
157 | 191 | ||
158 | @end itemize | 192 | @end itemize |
159 | |||
160 | |||
diff --git a/doc/libmicrohttpd-tutorial.texi b/doc/libmicrohttpd-tutorial.texi index 8fd7b566..de040fe8 100644 --- a/doc/libmicrohttpd-tutorial.texi +++ b/doc/libmicrohttpd-tutorial.texi | |||
@@ -24,7 +24,7 @@ updated @value{UPDATED}. | |||
24 | 24 | ||
25 | Copyright (c) 2008 Sebastian Gerhardt. | 25 | Copyright (c) 2008 Sebastian Gerhardt. |
26 | 26 | ||
27 | Copyright (c) 2010, 2011, 2012, 2013, 2016 Christian Grothoff. | 27 | Copyright (c) 2010, 2011, 2012, 2013, 2016, 2021 Christian Grothoff. |
28 | @quotation | 28 | @quotation |
29 | Permission is granted to copy, distribute and/or modify this document | 29 | Permission is granted to copy, distribute and/or modify this document |
30 | under the terms of the GNU Free Documentation License, Version 1.3 | 30 | under the terms of the GNU Free Documentation License, Version 1.3 |