Home | History | Annotate | Download | only in chapters
      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 occurs
     16 through secured lines (e.g., a embedded device directly attached to another via wire)
     17 and where the ability to embedd a password in the URI or to pass on a URI with a
     18 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 Internet browser,
     21 this implementation brings some problems about. For example, the URI including the password
     22 stays in the address field or at least in the history of the browser for anybody near enough to see. 
     23 It will also be inconvenient to add the password manually to any new URI when the browser does
     24 not know how to compose this automatically.
     25 
     26 At least the convenience issue can be addressed by employing the simplest built-in password
     27 facilities of HTTP compliant browsers, hence we want to start there. It will however turn out
     28 to have still severe weaknesses in terms of security which need consideration.
     29 
     30 Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617},
     31 we should finally abandon the bad practice of responding every request the first time our callback
     32 is called for a given connection. This is becoming more important now because the client and 
     33 the server will have to talk in a more bi-directional way than before to 
     34 
     35 But how can we tell whether the callback has been called before for the particular connection?
     36 Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will 
     37 also be "remembered" on the next call (for the same connection).
     38 Thus, we will generate no response until the parameter is non-null---implying the callback was
     39 called before at least once. We do not need to share information between different calls of the callback,
     40 so we can set the parameter to any adress that is assured to be not null. The pointer to the 
     41 @code{connection} structure will be pointing to a legal address, so we take this.
     42 
     43 The first time @code{answer_to_connection} is called, we will not even look at the headers.
     44 
     45 @verbatim
     46 static int 
     47 answer_to_connection (void *cls, struct MHD_Connection *connection,
     48                       const char *url, const char *method, const char *version, 
     49                       const char *upload_data, size_t *upload_data_size,
     50                       void **con_cls)
     51 {
     52   if (0 != strcmp(method, "GET")) return MHD_NO;
     53   if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;}
     54 
     55   ... 
     56   /* else respond accordingly */
     57   ...
     58 }
     59 @end verbatim
     60 @noindent
     61 
     62 Note how we lop off the connection on the first condition (no "GET" request), but return asking for more on 
     63 the other one with @code{MHD_YES}.
     64 With this minor change, we can proceed to implement the actual authentication process.
     65 
     66 @heading Request for authentication 
     67 
     68 Let us assume we had only files not intended to be handed out without the correct username/password,
     69 so every "GET" request will be challenged.
     70 @emph{RFC 2617} describes how the server shall ask for authentication by adding a
     71 @emph{WWW-Authenticate} response header with the name of the @emph{realm} protected.
     72 MHD can generate and queue such a failure response for you using
     73 the @code{MHD_queue_basic_auth_fail_response} API.  The only thing you need to do
     74 is construct a response with the error page to be shown to the user
     75 if he aborts basic authentication.  But first, you should check if the
     76 proper credentials were already supplied using the
     77 @code{MHD_basic_auth_get_username_password} call.
     78 
     79 Your code would then look like this:
     80 @verbatim
     81 static int
     82 answer_to_connection (void *cls, struct MHD_Connection *connection,
     83                       const char *url, const char *method,
     84                       const char *version, const char *upload_data,
     85                       size_t *upload_data_size, void **con_cls)
     86 {
     87   char *user;
     88   char *pass;
     89   int fail;
     90   struct MHD_Response *response;
     91 
     92   if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
     93     return MHD_NO;
     94   if (NULL == *con_cls)
     95     {
     96       *con_cls = connection;
     97       return MHD_YES;
     98     }
     99   pass = NULL;
    100   user = MHD_basic_auth_get_username_password (connection, &pass);
    101   fail = ( (user == NULL) ||
    102 	   (0 != strcmp (user, "root")) ||
    103 	   (0 != strcmp (pass, "pa$$w0rd") ) );  
    104   if (user != NULL) free (user);
    105   if (pass != NULL) free (pass);
    106   if (fail)
    107     {
    108       const char *page = "<html><body>Go away.</body></html>";
    109       response =
    110 	MHD_create_response_from_buffer (strlen (page), (void *) page, 
    111 				       MHD_RESPMEM_PERSISTENT);
    112       ret = MHD_queue_basic_auth_fail_response (connection,
    113 						"my realm",
    114 						response);
    115     }
    116   else
    117     {
    118       const char *page = "<html><body>A secret.</body></html>";
    119       response =
    120 	MHD_create_response_from_buffer (strlen (page), (void *) page, 
    121 				       MHD_RESPMEM_PERSISTENT);
    122       ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    123     }
    124   MHD_destroy_response (response);
    125   return ret;
    126 }
    127 @end verbatim
    128 
    129 See the @code{examples} directory for the complete example file.
    130 
    131 @heading Remarks
    132 For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a 
    133 response with a more precise status code instead of silently closing the connection. For example,
    134 failures of memory allocation are best reported as @emph{internal server error} and unexpected 
    135 authentication methods as @emph{400 bad request}.
    136 
    137 @heading Exercises
    138 @itemize @bullet
    139 @item
    140 Make the server respond to wrong credentials (but otherwise well-formed requests) with the recommended
    141 @emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
    142 same connection, close it and store the client's IP address for a certain time. (It is OK to check for
    143 expiration not until the main thread wakes up again on the next connection.) If the client fails
    144 authenticating three times during this period, add it to another list for which the 
    145 @code{AcceptPolicyCallback} function denies connection (temporally).
    146 
    147 @item
    148 With the network utility @code{netcat} connect and log the response of a "GET" request as you
    149 did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
    150 listen on the same port the server used to listen on and have it fake being the proper server by giving
    151 the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
    152 connecting to the actual server, browse to the eavesdropper and give the correct credentials.
    153 
    154 Copy and paste the encoded string you see in @code{netcat}'s output to some of the Base64 decode tools available online
    155 and see how both the user's name and password could be completely restored.
    156 
    157 @end itemize
    158 
    159 
    160