basicauthentication.inc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. A very simple approach feasible with the means already discussed would
  6. be to expect the password in the @emph{URI} string before granting access to
  7. the secured areas. The password could be separated from the actual resource identifier
  8. by a certain character, thus the request line might look like
  9. @verbatim
  10. GET /picture.png?mypassword
  11. @end verbatim
  12. @noindent
  13. In the rare situation where the client is customized enough and the connection
  14. occurs through secured lines (e.g., a embedded device directly attached to
  15. another via wire) and where the ability to embed a password in the URI or to
  16. pass on a URI with a password are desired, this can be a reasonable choice.
  17. But when it is assumed that the user connecting does so with an ordinary
  18. Internet browser, this implementation brings some problems about. For example,
  19. the URI including the password stays in the address field or at least in the
  20. history of the browser for anybody near enough to see. It will also be
  21. inconvenient to add the password manually to any new URI when the browser does
  22. not know how to compose this automatically.
  23. At least the convenience issue can be addressed by employing the simplest
  24. built-in password facilities of HTTP compliant browsers, hence we want to
  25. start there. It will, however, turn out to have still severe weaknesses in
  26. terms of security which need consideration.
  27. Before we will start implementing @emph{Basic Authentication} as described in
  28. @emph{RFC 2617}, we will also abandon the simplistic and generally
  29. problematic practice of responding every request the first time our callback
  30. is called for a given connection. Queuing a response upon the first request
  31. is akin to generating an error response (even if it is a "200 OK" reply!).
  32. The reason is that MHD usually calls the callback in three phases:
  33. @enumerate
  34. @item
  35. First, to initially tell the application about the connection and inquire whether
  36. it is OK to proceed. This call typically happens before the client could upload
  37. the request body, and can be used to tell the client to not proceed with the
  38. upload (if the client requested "Expect: 100 Continue"). Applications may queue
  39. a reply at this point, but it will force the connection to be closed and thus
  40. prevent keep-alive / pipelining, which is generally a bad idea. Applications
  41. wanting to proceed with the request throughout the other phases should just return
  42. "MHD_YES" and not queue any response. Note that when an application suspends
  43. a connection in this callback, the phase does not advance and the application
  44. will be called again in this first phase.
  45. @item
  46. Next, to tell the application about upload data provided by the client.
  47. In this phase, the application may not queue replies, and trying to do so
  48. will result in MHD returning an error code from @code{MHD_queue_response}.
  49. If there is no upload data, this phase is skipped.
  50. @item
  51. Finally, to obtain a regular response from the application. This can be
  52. almost any type of response, including ones indicating failures. The
  53. one exception is a "100 Continue" response, which applications must never
  54. generate: MHD generates that response automatically when necessary in the
  55. first phase. If the application does not queue a response, MHD may call
  56. the callback repeatedly (depending a bit on the threading model, the
  57. application should suspend the connection).
  58. @end enumerate
  59. But how can we tell whether the callback has been called before for the
  60. particular request? Initially, the pointer this parameter references is
  61. set by @emph{MHD} in the callback. But it will also be "remembered" on the
  62. next call (for the same request). Thus, we can use the @code{req_cls}
  63. location to keep track of the request state. For now, we will simply
  64. generate no response until the parameter is non-null---implying the callback
  65. was called before at least once. We do not need to share information between
  66. different calls of the callback, so we can set the parameter to any address
  67. that is assured to be not null. The pointer to the @code{connection} structure
  68. will be pointing to a legal address, so we take this.
  69. The first time @code{answer_to_connection} is called, we will not even look at the headers.
  70. @verbatim
  71. static int
  72. answer_to_connection (void *cls, struct MHD_Connection *connection,
  73. const char *url, const char *method, const char *version,
  74. const char *upload_data, size_t *upload_data_size,
  75. void **req_cls)
  76. {
  77. if (0 != strcmp(method, "GET")) return MHD_NO;
  78. if (NULL == *req_cls) {*req_cls = connection; return MHD_YES;}
  79. ...
  80. /* else respond accordingly */
  81. ...
  82. }
  83. @end verbatim
  84. @noindent
  85. Note how we lop off the connection on the first condition (no "GET" request),
  86. but return asking for more on the other one with @code{MHD_YES}. With this
  87. minor change, we can proceed to implement the actual authentication process.
  88. @heading Request for authentication
  89. Let us assume we had only files not intended to be handed out without the
  90. correct username/password, so every "GET" request will be challenged.
  91. @emph{RFC 7617} describes how the server shall ask for authentication by
  92. adding a @emph{WWW-Authenticate} response header with the name of the
  93. @emph{realm} protected. MHD can generate and queue such a failure response
  94. for you using the @code{MHD_queue_basic_auth_fail_response} API. The only
  95. thing you need to do is construct a response with the error page to be shown
  96. to the user if he aborts basic authentication. But first, you should check if
  97. the proper credentials were already supplied using the
  98. @code{MHD_basic_auth_get_username_password} call.
  99. Your code would then look like this:
  100. @verbatim
  101. static enum MHD_Result
  102. answer_to_connection (void *cls, struct MHD_Connection *connection,
  103. const char *url, const char *method,
  104. const char *version, const char *upload_data,
  105. size_t *upload_data_size, void **req_cls)
  106. {
  107. struct MHD_BasicAuthInfo *auth_info;
  108. enum MHD_Result ret;
  109. struct MHD_Response *response;
  110. if (0 != strcmp (method, "GET"))
  111. return MHD_NO;
  112. if (NULL == *req_cls)
  113. {
  114. *req_cls = connection;
  115. return MHD_YES;
  116. }
  117. auth_info = MHD_basic_auth_get_username_password3 (connection);
  118. if (NULL == auth_info)
  119. {
  120. static const char *page =
  121. "<html><body>Authorization required</body></html>";
  122. response = MHD_create_response_from_buffer_static (strlen (page), page);
  123. ret = MHD_queue_basic_auth_fail_response3 (connection,
  124. "admins",
  125. MHD_YES,
  126. response);
  127. }
  128. else if ((strlen ("root") != auth_info->username_len) ||
  129. (0 != memcmp (auth_info->username, "root",
  130. auth_info->username_len)) ||
  131. /* The next check against NULL is optional,
  132. * if 'password' is NULL then 'password_len' is always zero. */
  133. (NULL == auth_info->password) ||
  134. (strlen ("pa$$w0rd") != auth_info->password_len) ||
  135. (0 != memcmp (auth_info->password, "pa$$w0rd",
  136. auth_info->password_len)))
  137. {
  138. static const char *page =
  139. "<html><body>Wrong username or password</body></html>";
  140. response = MHD_create_response_from_buffer_static (strlen (page), page);
  141. ret = MHD_queue_basic_auth_fail_response3 (connection,
  142. "admins",
  143. MHD_YES,
  144. response);
  145. }
  146. else
  147. {
  148. static const char *page = "<html><body>A secret.</body></html>";
  149. response = MHD_create_response_from_buffer_static (strlen (page), page);
  150. ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
  151. }
  152. if (NULL != auth_info)
  153. MHD_free (auth_info);
  154. MHD_destroy_response (response);
  155. return ret;
  156. }
  157. @end verbatim
  158. See the @code{examples} directory for the complete example file.
  159. @heading Remarks
  160. For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a
  161. response with a more precise status code instead of silently closing the connection. For example,
  162. failures of memory allocation are best reported as @emph{internal server error} and unexpected
  163. authentication methods as @emph{400 bad request}.
  164. @heading Exercises
  165. @itemize @bullet
  166. @item
  167. Make the server respond to wrong credentials (but otherwise well-formed requests) with the recommended
  168. @emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
  169. same connection, close it and store the client's IP address for a certain time. (It is OK to check for
  170. expiration not until the main thread wakes up again on the next connection.) If the client fails
  171. authenticating three times during this period, add it to another list for which the
  172. @code{AcceptPolicyCallback} function denies connection (temporally).
  173. @item
  174. With the network utility @code{netcat} connect and log the response of a "GET" request as you
  175. did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
  176. listen on the same port the server used to listen on and have it fake being the proper server by giving
  177. the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
  178. connecting to the actual server, browse to the eavesdropper and give the correct credentials.
  179. Copy and paste the encoded string you see in @code{netcat}'s output to some of the Base64 decode tools available online
  180. and see how both the user's name and password could be completely restored.
  181. @end itemize