libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

basicauthentication.inc (10117B)


      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 the rare situation where the client is customized enough and the connection
     16 occurs through secured lines (e.g., a embedded device directly attached to
     17 another via wire) and where the ability to embed a password in the URI or to
     18 pass on a URI with a password are desired, this can be a reasonable choice.
     19 
     20 But when it is assumed that the user connecting does so with an ordinary
     21 Internet browser, this implementation brings some problems about. For example,
     22 the URI including the password stays in the address field or at least in the
     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
     25 not know how to compose this automatically.
     26 
     27 At least the convenience issue can be addressed by employing the simplest
     28 built-in password facilities of HTTP compliant browsers, hence we want to
     29 start there. It will, however, turn out to have still severe weaknesses in
     30 terms of security which need consideration.
     31 
     32 Before we will start implementing @emph{Basic Authentication} as described in
     33 @emph{RFC 7617}, we will also abandon the simplistic and generally
     34 problematic practice of responding every request the first time our callback
     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:
     38 
     39 @enumerate
     40 @item
     41 First, to initially tell the application about the connection and inquire whether
     42 it is OK to proceed. This call typically happens before the client could upload
     43 the request body, and can be used to tell the client to not proceed with the
     44 upload (if the client requested "Expect: 100 Continue"). Applications may queue
     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 request?  This is what the @code{req_cls} parameter of the callback
     68 is for: initially, the pointer it references is set to NULL by @emph{MHD}.
     69 But whatever we store there will be "remembered" on the
     70 next call (for the same request).  Thus, we can use the @code{req_cls}
     71 location to keep track of the request state.  For now, we will simply
     72 generate no response until the parameter is non-null---implying the callback
     73 was called before at least once. We do not need to share information between
     74 different calls of the callback, so we can set the parameter to any address
     75 that is assured to be not null. The pointer to the @code{connection} structure
     76 will be pointing to a legal address, so we take this.
     77 
     78 The first time @code{answer_to_connection} is called, we will not even look at the headers.
     79 
     80 @verbatim
     81 static enum MHD_Result
     82 answer_to_connection (void *cls, struct MHD_Connection *connection,
     83                       const char *url, const char *method, const char *version,
     84                       const char *upload_data, size_t *upload_data_size,
     85                       void **req_cls)
     86 {
     87   if (0 != strcmp(method, "GET")) return MHD_NO;
     88   if (NULL == *req_cls) {*req_cls = connection; return MHD_YES;}
     89 
     90   ...
     91   /* else respond accordingly */
     92   ...
     93 }
     94 @end verbatim
     95 @noindent
     96 
     97 Note how we lop off the connection on the first condition (no "GET" request),
     98 but return asking for more on the other one with @code{MHD_YES}.  With this
     99 minor change, we can proceed to implement the actual authentication process.
    100 
    101 @heading Request for authentication
    102 
    103 Let us assume we had only files not intended to be handed out without the
    104 correct username/password, so every "GET" request will be challenged.
    105 @emph{RFC 7617} describes how the server shall ask for authentication by
    106 adding a @emph{WWW-Authenticate} response header with the name of the
    107 @emph{realm} protected.  MHD can generate and queue such a failure response
    108 for you using the @code{MHD_queue_basic_auth_required_response3} API.  The only
    109 thing you need to do is construct a response with the error page to be shown
    110 to the user if he aborts basic authentication.  But first, you should check if
    111 the proper credentials were already supplied using the
    112 @code{MHD_basic_auth_get_username_password3} call.  (The older
    113 @code{MHD_queue_basic_auth_fail_response} and
    114 @code{MHD_basic_auth_get_username_password} calls are deprecated.)
    115 
    116 Your code would then look like this:
    117 @verbatim
    118 static enum MHD_Result
    119 answer_to_connection (void *cls, struct MHD_Connection *connection,
    120                       const char *url, const char *method,
    121                       const char *version, const char *upload_data,
    122                       size_t *upload_data_size, void **req_cls)
    123 {
    124   struct MHD_BasicAuthInfo *auth_info;
    125   enum MHD_Result ret;
    126   struct MHD_Response *response;
    127 
    128   if (0 != strcmp (method, "GET"))
    129     return MHD_NO;
    130   if (NULL == *req_cls)
    131   {
    132     *req_cls = connection;
    133     return MHD_YES;
    134   }
    135   auth_info = MHD_basic_auth_get_username_password3 (connection);
    136   if (NULL == auth_info)
    137   {
    138     static const char *page =
    139       "<html><body>Authorization required</body></html>";
    140     response = MHD_create_response_from_buffer_static (strlen (page), page);
    141     ret = MHD_queue_basic_auth_required_response3 (connection,
    142                                                    "admins",
    143                                                    MHD_YES,
    144                                                    response);
    145   }
    146   else if ((strlen ("root") != auth_info->username_len) ||
    147            (0 != memcmp (auth_info->username, "root",
    148                          auth_info->username_len)) ||
    149            /* The next check against NULL is optional,
    150             * if 'password' is NULL then 'password_len' is always zero. */
    151            (NULL == auth_info->password) ||
    152            (strlen ("pa$$w0rd") != auth_info->password_len) ||
    153            (0 != memcmp (auth_info->password, "pa$$w0rd",
    154                          auth_info->password_len)))
    155   {
    156     static const char *page =
    157       "<html><body>Wrong username or password</body></html>";
    158     response = MHD_create_response_from_buffer_static (strlen (page), page);
    159     ret = MHD_queue_basic_auth_required_response3 (connection,
    160                                                    "admins",
    161                                                    MHD_YES,
    162                                                    response);
    163   }
    164   else
    165   {
    166     static const char *page = "<html><body>A secret.</body></html>";
    167     response = MHD_create_response_from_buffer_static (strlen (page), page);
    168     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    169   }
    170   if (NULL != auth_info)
    171     MHD_free (auth_info);
    172   MHD_destroy_response (response);
    173   return ret;
    174 }
    175 @end verbatim
    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 otherwise well-formed 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 for which the
    193 @code{AcceptPolicyCallback} function denies connection (temporally).
    194 
    195 @item
    196 With the network utility @code{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 @code{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