paivana-httpd_daemon.c (10976B)
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 MHD_HTTP_METHOD_POST)) ) 130 { 131 rc->is_paivana = true; 132 } 133 if (rc->is_paivana) 134 { 135 if (PH_no_check) 136 { 137 /* paywall disabled, 501 */ 138 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 139 "Paywall disabled, refusing to respond to %s\n", 140 url); 141 return TALER_MHD_reply_with_error (rc->connection, 142 MHD_HTTP_NOT_IMPLEMENTED, 143 TALER_EC_PAIVANA_PAYWALL_DISABLED, 144 NULL); 145 } 146 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 147 "Client POSTed payment, checking validity\n"); 148 if (NULL == rc->hp) 149 rc->hp = PAIVANA_HTTPD_payment_create (rc->connection); 150 return PAIVANA_HTTPD_payment_handle (rc->hp, 151 upload_data, 152 upload_data_size); 153 } 154 155 if (PH_have_whitelist_ex && (! rc->do_forward)) 156 { 157 rc->do_forward = (0 == 158 regexec (&PH_whitelist_ex, 159 url, 160 0, 161 NULL, 162 0)); 163 } 164 165 if (rc->do_forward) 166 goto do_forward; 167 168 if ( (0 == strncmp (url, 169 "/.well-known/paivana/templates/", 170 strlen ("/.well-known/paivana/templates/"))) && 171 (0 == strcasecmp (meth, 172 MHD_HTTP_METHOD_GET)) ) 173 { 174 const char *id = &url[strlen ("/.well-known/paivana/templates/")]; 175 176 return PAIVANA_HTTPD_return_template (rc->connection, 177 id); 178 } 179 180 if (! PAIVANA_HTTPD_get_base_url (con, 181 &buf)) 182 { 183 GNUNET_break (0); 184 return TALER_MHD_reply_with_error ( 185 con, 186 MHD_HTTP_BAD_REQUEST, 187 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 188 "Host or X-Forwarded-Host required"); 189 } 190 GNUNET_buffer_write_str (&buf, 191 url); 192 website = GNUNET_buffer_reap_str (&buf); 193 cookie = MHD_lookup_connection_value (con, 194 MHD_COOKIE_KIND, 195 "Paivana-Cookie"); 196 if (NULL != cookie) 197 { 198 void *ca = NULL; 199 size_t ca_len = 0; 200 201 /* If we cannot get the client address, we just 202 use 0/NULL and log an error. */ 203 GNUNET_break (PAIVANA_HTTPD_get_client_address (con, 204 &ca, 205 &ca_len)); 206 ok = PAIVANA_HTTPD_check_cookie (cookie, 207 website, 208 ca_len, 209 ca); 210 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 211 "Client sent cookie %s for %s: %s\n", 212 cookie, 213 website, 214 ok ? "good" : "invalid"); 215 GNUNET_free (ca); 216 } 217 if (! ok) 218 { 219 enum GNUNET_GenericReturnValue ret; 220 221 ret = PAIVANA_HTTPD_search_templates (con, 222 website); 223 if (GNUNET_SYSERR != ret) 224 { 225 GNUNET_free (website); 226 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 227 "Payment required, sending paywall page %s\n", 228 (GNUNET_OK == ret) ? "ok" : "failed"); 229 return (GNUNET_OK == ret) ? MHD_YES : MHD_NO; 230 } 231 } 232 GNUNET_free (website); 233 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 234 "Request OK, no paywall applies!\n"); 235 rc->do_forward = true; 236 do_forward: 237 if (NULL == rc->hr) 238 rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection, 239 rc->url); 240 return PAIVANA_HTTPD_reverse (rc->hr, 241 con, 242 url, 243 meth, 244 ver, 245 upload_data, 246 upload_data_size); 247 } 248 249 250 /** 251 * Function called when MHD decides that we 252 * are done with a request. 253 * 254 * @param cls NULL 255 * @param connection connection handle 256 * @param con_cls value as set by the last call to 257 * the MHD_AccessHandlerCallback, should be 258 * our `struct RequestContext *` (created in `mhd_log_callback()`) 259 * @param toe reason for request termination (ignored) 260 */ 261 static void 262 mhd_completed_cb (void *cls, 263 struct MHD_Connection *connection, 264 void **con_cls, 265 enum MHD_RequestTerminationCode toe) 266 { 267 struct RequestContext *rc = *con_cls; 268 269 (void) cls; 270 (void) connection; 271 if (NULL == rc) 272 return; 273 if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) 274 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 275 "MHD encountered error handling request to %s: %d\n", 276 rc->url, 277 toe); 278 if (NULL != rc->hr) 279 PAIVANA_HTTPD_reverse_cleanup (rc->hr); 280 if (NULL != rc->hp) 281 PAIVANA_HTTPD_payment_destroy (rc->hp); 282 GNUNET_free (rc->url); 283 GNUNET_free (rc); 284 *con_cls = NULL; 285 } 286 287 288 /** 289 * Function called when MHD first processes an incoming connection. 290 * Gives us the respective URI information. 291 * 292 * We use this to associate the `struct MHD_Connection` with our 293 * internal `struct HttpRequest` data structure (by checking 294 * for matching sockets). 295 * 296 * @param cls the HTTP server handle (a `struct MhdHttpList`) 297 * @param url the URL that is being requested 298 * @param connection MHD connection object for the request 299 * @return the `struct RequestContext` that this @a connection is for 300 */ 301 static void * 302 mhd_log_callback (void *cls, 303 const char *url, 304 struct MHD_Connection *connection) 305 { 306 struct RequestContext *rc; 307 308 rc = GNUNET_new (struct RequestContext); 309 rc->connection = connection; 310 rc->url = GNUNET_strdup (url); 311 rc->do_forward = (1 == PH_no_check); 312 return rc; 313 } 314 315 316 /** 317 * Callback invoked on every listen socket to start the 318 * respective MHD HTTP daemon. 319 * 320 * @param cls unused 321 * @param lsock the listen socket 322 */ 323 static void 324 start_daemon (void *cls, 325 int lsock) 326 { 327 struct MHD_Daemon *mhd; 328 329 (void) cls; 330 GNUNET_assert (-1 != lsock); 331 mhd = MHD_start_daemon ( 332 MHD_USE_DEBUG 333 | MHD_ALLOW_SUSPEND_RESUME 334 | MHD_USE_DUAL_STACK, 335 0, 336 NULL, NULL, 337 &create_response, NULL, 338 MHD_OPTION_LISTEN_SOCKET, 339 lsock, 340 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16, 341 MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL, 342 MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL, 343 MHD_OPTION_END); 344 345 if (NULL == mhd) 346 { 347 GNUNET_break (0); 348 GNUNET_SCHEDULER_shutdown (); 349 return; 350 } 351 have_daemons = true; 352 TALER_MHD_daemon_start (mhd); 353 } 354 355 356 void 357 PAIVANA_HTTPD_serve_requests () 358 { 359 enum GNUNET_GenericReturnValue ret; 360 361 ret = TALER_MHD_listen_bind (PH_cfg, 362 "paivana", 363 &start_daemon, 364 NULL); 365 switch (ret) 366 { 367 case GNUNET_SYSERR: 368 PH_global_ret = EXIT_NOTCONFIGURED; 369 GNUNET_SCHEDULER_shutdown (); 370 return; 371 case GNUNET_NO: 372 if (! have_daemons) 373 { 374 PH_global_ret = EXIT_NOTCONFIGURED; 375 GNUNET_SCHEDULER_shutdown (); 376 return; 377 } 378 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 379 "Could not open all configured listen sockets\n"); 380 break; 381 case GNUNET_OK: 382 break; 383 } 384 }