libmicrohttpd

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

basicauthentication.inc (9880B)


      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 2617}, 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?  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 request).  Thus, we can use the @code{req_cls}
     70 location to keep track of the request 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.
     76 
     77 The first time @code{answer_to_connection} is called, we will not even look at the headers.
     78 
     79 @verbatim
     80 static int
     81 answer_to_connection (void *cls, struct MHD_Connection *connection,
     82                       const char *url, const char *method, const char *version,
     83                       const char *upload_data, size_t *upload_data_size,
     84                       void **req_cls)
     85 {
     86   if (0 != strcmp(method, "GET")) return MHD_NO;
     87   if (NULL == *req_cls) {*req_cls = connection; return MHD_YES;}
     88 
     89   ...
     90   /* else respond accordingly */
     91   ...
     92 }
     93 @end verbatim
     94 @noindent
     95 
     96 Note how we lop off the connection on the first condition (no "GET" request),
     97 but return asking for more on the other one with @code{MHD_YES}.  With this
     98 minor change, we can proceed to implement the actual authentication process.
     99 
    100 @heading Request for authentication
    101 
    102 Let us assume we had only files not intended to be handed out without the
    103 correct username/password, so every "GET" request will be challenged.
    104 @emph{RFC 7617} describes how the server shall ask for authentication by
    105 adding a @emph{WWW-Authenticate} response header with the name of the
    106 @emph{realm} protected.  MHD can generate and queue such a failure response
    107 for you using the @code{MHD_queue_basic_auth_fail_response} API.  The only
    108 thing you need to do is construct a response with the error page to be shown
    109 to the user if he aborts basic authentication.  But first, you should check if
    110 the proper credentials were already supplied using the
    111 @code{MHD_basic_auth_get_username_password} call.
    112 
    113 Your code would then look like this:
    114 @verbatim
    115 static enum MHD_Result
    116 answer_to_connection (void *cls, struct MHD_Connection *connection,
    117                       const char *url, const char *method,
    118                       const char *version, const char *upload_data,
    119                       size_t *upload_data_size, void **req_cls)
    120 {
    121   struct MHD_BasicAuthInfo *auth_info;
    122   enum MHD_Result ret;
    123   struct MHD_Response *response;
    124 
    125   if (0 != strcmp (method, "GET"))
    126     return MHD_NO;
    127   if (NULL == *req_cls)
    128   {
    129     *req_cls = connection;
    130     return MHD_YES;
    131   }
    132   auth_info = MHD_basic_auth_get_username_password3 (connection);
    133   if (NULL == auth_info)
    134   {
    135     static const char *page =
    136       "<html><body>Authorization required</body></html>";
    137     response = MHD_create_response_from_buffer_static (strlen (page), page);
    138     ret = MHD_queue_basic_auth_fail_response3 (connection,
    139                                                "admins",
    140                                                MHD_YES,
    141                                                response);
    142   }
    143   else if ((strlen ("root") != auth_info->username_len) ||
    144            (0 != memcmp (auth_info->username, "root",
    145                          auth_info->username_len)) ||
    146            /* The next check against NULL is optional,
    147             * if 'password' is NULL then 'password_len' is always zero. */
    148            (NULL == auth_info->password) ||
    149            (strlen ("pa$$w0rd") != auth_info->password_len) ||
    150            (0 != memcmp (auth_info->password, "pa$$w0rd",
    151                          auth_info->password_len)))
    152   {
    153     static const char *page =
    154       "<html><body>Wrong username or password</body></html>";
    155     response = MHD_create_response_from_buffer_static (strlen (page), page);
    156     ret = MHD_queue_basic_auth_fail_response3 (connection,
    157                                                "admins",
    158                                                MHD_YES,
    159                                                response);
    160   }
    161   else
    162   {
    163     static const char *page = "<html><body>A secret.</body></html>";
    164     response = MHD_create_response_from_buffer_static (strlen (page), page);
    165     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    166   }
    167   if (NULL != auth_info)
    168     MHD_free (auth_info);
    169   MHD_destroy_response (response);
    170   return ret;
    171 }
    172 @end verbatim
    173 
    174 See the @code{examples} directory for the complete example file.
    175 
    176 @heading Remarks
    177 For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a
    178 response with a more precise status code instead of silently closing the connection. For example,
    179 failures of memory allocation are best reported as @emph{internal server error} and unexpected
    180 authentication methods as @emph{400 bad request}.
    181 
    182 @heading Exercises
    183 @itemize @bullet
    184 @item
    185 Make the server respond to wrong credentials (but otherwise well-formed requests) with the recommended
    186 @emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
    187 same connection, close it and store the client's IP address for a certain time. (It is OK to check for
    188 expiration not until the main thread wakes up again on the next connection.) If the client fails
    189 authenticating three times during this period, add it to another list for which the
    190 @code{AcceptPolicyCallback} function denies connection (temporally).
    191 
    192 @item
    193 With the network utility @code{netcat} connect and log the response of a "GET" request as you
    194 did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
    195 listen on the same port the server used to listen on and have it fake being the proper server by giving
    196 the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
    197 connecting to the actual server, browse to the eavesdropper and give the correct credentials.
    198 
    199 Copy and paste the encoded string you see in @code{netcat}'s output to some of the Base64 decode tools available online
    200 and see how both the user's name and password could be completely restored.
    201 
    202 @end itemize