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