paivana-httpd_daemon.c (9602B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2026 Taler Systems SA 4 5 Paivana is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 8 3, or (at your option) any later version. 9 10 Paivana is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty 12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 13 the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with Paivana; see the file COPYING. If not, 17 write to the Free Software Foundation, Inc., 51 Franklin 18 Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @author Christian Grothoff 23 * @file paivana-httpd_daemon.c 24 * @brief daemon functions 25 */ 26 27 #include "platform.h" 28 #include <curl/curl.h> 29 #include <gnunet/gnunet_util_lib.h> 30 #include <gnunet/gnunet_curl_lib.h> 31 #include <taler/taler_mhd_lib.h> 32 #include "paivana-httpd_cookie.h" 33 #include "paivana-httpd_daemon.h" 34 #include "paivana-httpd_helper.h" 35 #include "paivana-httpd_pay.h" 36 #include "paivana-httpd_reverse.h" 37 #include "paivana-httpd_templates.h" 38 39 40 struct RequestContext 41 { 42 43 /** 44 * HTTP connection to the client. 45 */ 46 struct MHD_Connection *connection; 47 48 /** 49 * Handle for request forwarding as reverse proxy. 50 */ 51 struct HttpRequest *hr; 52 53 /** 54 * Handle for processing actual payment. 55 */ 56 struct PayRequest *hp; 57 58 /** 59 * Full request URL. 60 */ 61 char *url; 62 63 /** 64 * True if this is a POST to the .well-known/paivana endpoint. 65 */ 66 bool is_paivana; 67 68 /** 69 * We are past the paywall, forward to client. 70 */ 71 bool do_forward; 72 }; 73 74 75 /** 76 * Set to true if we started a daemon. 77 */ 78 static bool have_daemons; 79 80 81 /** 82 * Main MHD callback for handling requests. 83 * 84 * @param cls unused 85 * @param con MHD connection handle 86 * @param url the url in the request 87 * @param meth the HTTP method used ("GET", "PUT", etc.) 88 * @param ver the HTTP version string (i.e. "HTTP/1.1") 89 * @param upload_data the data being uploaded (excluding HEADERS, 90 * for a POST that fits into memory and that is encoded 91 * with a supported encoding, the POST data will NOT be 92 * given in upload_data and is instead available as 93 * part of MHD_get_connection_values; very large POST 94 * data *will* be made available incrementally in 95 * upload_data) 96 * @param upload_data_size set initially to the size of the 97 * @a upload_data provided; the method must update this 98 * value to the number of bytes NOT processed; 99 * @param con_cls pointer to location where we store the 100 * 'struct Request' 101 * @return #MHD_YES if the connection was handled successfully, 102 * #MHD_NO if the socket must be closed due to a serious 103 * error while handling the request 104 */ 105 static enum MHD_Result 106 create_response (void *cls, 107 struct MHD_Connection *con, 108 const char *url, 109 const char *meth, 110 const char *ver, 111 const char *upload_data, 112 size_t *upload_data_size, 113 void **con_cls) 114 { 115 struct RequestContext *rc = *con_cls; 116 const char *cookie; 117 bool ok = false; 118 struct GNUNET_Buffer buf; 119 char *website; 120 121 (void) cls; 122 memset (&buf, 123 0, 124 sizeof (buf)); 125 if ( (! rc->is_paivana) && 126 (0 == strcmp (url, 127 ".well-known/paivana")) && 128 (0 == strcasecmp (meth, 129 "POST")) ) 130 { 131 rc->is_paivana = true; 132 } 133 if (rc->is_paivana) 134 { 135 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 136 "Client POSTed payment, checking validity\n"); 137 if (NULL == rc->hp) 138 rc->hp = PAIVANA_HTTPD_payment_create (rc->connection); 139 return PAIVANA_HTTPD_payment_handle (rc->hp, 140 upload_data, 141 upload_data_size); 142 } 143 144 if (rc->do_forward) 145 { 146 if (NULL == rc->hr) 147 rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection, 148 rc->url); 149 return PAIVANA_HTTPD_reverse (rc->hr, 150 con, 151 url, 152 meth, 153 ver, 154 upload_data, 155 upload_data_size); 156 } 157 158 if (! PAIVANA_HTTPD_get_base_url (con, 159 &buf)) 160 { 161 GNUNET_break (0); 162 return TALER_MHD_reply_with_error ( 163 con, 164 MHD_HTTP_BAD_REQUEST, 165 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 166 "Host or X-Forwarded-Host required"); 167 } 168 GNUNET_buffer_write_str (&buf, 169 url); 170 website = GNUNET_buffer_reap_str (&buf); 171 172 cookie = MHD_lookup_connection_value (con, 173 MHD_COOKIE_KIND, 174 "Paivana-Cookie"); 175 if (NULL != cookie) 176 { 177 void *ca; 178 size_t ca_len; 179 180 GNUNET_break (PAIVANA_HTTPD_get_client_address (con, 181 &ca, 182 &ca_len)); 183 ok = PAIVANA_HTTPD_check_cookie (cookie, 184 website, 185 ca_len, 186 ca); 187 GNUNET_free (ca); 188 } 189 if (! ok) 190 { 191 enum GNUNET_GenericReturnValue ret; 192 193 ret = PAIVANA_HTTPD_search_templates (con, 194 website); 195 GNUNET_free (website); 196 if (GNUNET_SYSERR != ret) 197 { 198 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 199 "Payment required, sending paywall page %s\n", 200 (GNUNET_OK == ret) ? "ok" : "failed"); 201 return (GNUNET_OK == ret) ? MHD_YES : MHD_NO; 202 } 203 } 204 GNUNET_free (website); 205 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 206 "Request OK, no paywall applies!\n"); 207 rc->do_forward = true; 208 /* FIXME: code for 100 continue suppression should go here! */ 209 return MHD_YES; 210 } 211 212 213 /** 214 * Function called when MHD decides that we 215 * are done with a request. 216 * 217 * @param cls NULL 218 * @param connection connection handle 219 * @param con_cls value as set by the last call to 220 * the MHD_AccessHandlerCallback, should be 221 * our `struct RequestContext *` (created in `mhd_log_callback()`) 222 * @param toe reason for request termination (ignored) 223 */ 224 static void 225 mhd_completed_cb (void *cls, 226 struct MHD_Connection *connection, 227 void **con_cls, 228 enum MHD_RequestTerminationCode toe) 229 { 230 struct RequestContext *rc = *con_cls; 231 232 (void) cls; 233 (void) connection; 234 if (NULL == rc) 235 return; 236 if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) 237 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 238 "MHD encountered error handling request to %s: %d\n", 239 rc->url, 240 toe); 241 if (NULL != rc->hr) 242 PAIVANA_HTTPD_reverse_cleanup (rc->hr); 243 if (NULL != rc->hp) 244 PAIVANA_HTTPD_payment_destroy (rc->hp); 245 GNUNET_free (rc->url); 246 GNUNET_free (rc); 247 *con_cls = NULL; 248 } 249 250 251 /** 252 * Function called when MHD first processes an incoming connection. 253 * Gives us the respective URI information. 254 * 255 * We use this to associate the `struct MHD_Connection` with our 256 * internal `struct HttpRequest` data structure (by checking 257 * for matching sockets). 258 * 259 * @param cls the HTTP server handle (a `struct MhdHttpList`) 260 * @param url the URL that is being requested 261 * @param connection MHD connection object for the request 262 * @return the `struct RequestContext` that this @a connection is for 263 */ 264 static void * 265 mhd_log_callback (void *cls, 266 const char *url, 267 struct MHD_Connection *connection) 268 { 269 struct RequestContext *rc; 270 271 rc = GNUNET_new (struct RequestContext); 272 rc->connection = connection; 273 rc->url = GNUNET_strdup (url); 274 rc->do_forward = (1 == PH_no_check); 275 return rc; 276 } 277 278 279 /** 280 * Callback invoked on every listen socket to start the 281 * respective MHD HTTP daemon. 282 * 283 * @param cls unused 284 * @param lsock the listen socket 285 */ 286 static void 287 start_daemon (void *cls, 288 int lsock) 289 { 290 struct MHD_Daemon *mhd; 291 292 (void) cls; 293 GNUNET_assert (-1 != lsock); 294 mhd = MHD_start_daemon ( 295 MHD_USE_DEBUG 296 | MHD_ALLOW_SUSPEND_RESUME 297 | MHD_USE_DUAL_STACK, 298 0, 299 NULL, NULL, 300 &create_response, NULL, 301 MHD_OPTION_LISTEN_SOCKET, 302 lsock, 303 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16, 304 MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL, 305 MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL, 306 MHD_OPTION_END); 307 308 if (NULL == mhd) 309 { 310 GNUNET_break (0); 311 GNUNET_SCHEDULER_shutdown (); 312 return; 313 } 314 have_daemons = true; 315 TALER_MHD_daemon_start (mhd); 316 } 317 318 319 void 320 PAIVANA_HTTPD_serve_requests () 321 { 322 enum GNUNET_GenericReturnValue ret; 323 324 ret = TALER_MHD_listen_bind (PH_cfg, 325 "paivana", 326 &start_daemon, 327 NULL); 328 switch (ret) 329 { 330 case GNUNET_SYSERR: 331 PH_global_ret = EXIT_NOTCONFIGURED; 332 GNUNET_SCHEDULER_shutdown (); 333 return; 334 case GNUNET_NO: 335 if (! have_daemons) 336 { 337 PH_global_ret = EXIT_NOTCONFIGURED; 338 GNUNET_SCHEDULER_shutdown (); 339 return; 340 } 341 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 342 "Could not open all configured listen sockets\n"); 343 break; 344 case GNUNET_OK: 345 break; 346 } 347 }