mailbox.go (23983B)
1 // This file is part of taler-mailbox, the Taler Mailbox implementation. 2 // Copyright (C) 2022 Martin Schanzenbach 3 // 4 // Taler-mailbox is free software: you can redistribute it and/or modify it 5 // under the terms of the GNU Affero General Public License as published 6 // by the Free Software Foundation, either version 3 of the License, 7 // or (at your option) any later version. 8 // 9 // Taler-mailbox is distributed in the hope that it will be useful, but 10 // WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 // Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 // 17 // SPDX-License-Identifier: AGPL3.0-or-later 18 19 // Package mailbox is a GNU Taler service. See https://docs.taler.net/core/api-mailbox.html 20 package mailbox 21 22 import ( 23 "crypto/ed25519" 24 "crypto/sha512" 25 "encoding/binary" 26 "encoding/json" 27 "fmt" 28 "io" 29 "log" 30 "net/http" 31 "os" 32 "strconv" 33 "strings" 34 "time" 35 36 "github.com/gorilla/mux" 37 "github.com/schanzen/taler-go/pkg/merchant" 38 tos "github.com/schanzen/taler-go/pkg/rest" 39 talerutil "github.com/schanzen/taler-go/pkg/util" 40 "taler.net/taler-mailbox/internal/gana" 41 "taler.net/taler-mailbox/internal/util" 42 ) 43 44 type LogLevel int 45 46 const ( 47 LogError LogLevel = iota 48 LogWarning 49 LogInfo 50 LogDebug 51 ) 52 53 var LoglevelStringMap = map[LogLevel]string{ 54 LogDebug: "DEBUG", 55 LogError: "ERROR", 56 LogWarning: "WARN", 57 LogInfo: "INFO", 58 } 59 60 type MailboxConfig struct { 61 // libtool-style representation of the Mailbox protocol version, see 62 // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning 63 // The format is "current:revision:age". 64 LibtoolVersion string 65 66 // Version 67 Version string 68 69 // Data home 70 Datahome string 71 72 // Configuration 73 Ini talerutil.TalerConfiguration 74 75 // The database connection to use 76 DB *MailboxDatabase 77 78 // Merchant connection 79 Merchant merchant.Merchant 80 81 // Loglevel 82 Loglevel LogLevel 83 } 84 85 // Mailbox is the primary object of the Mailbox service 86 type Mailbox struct { 87 88 // The main router 89 Router *mux.Router 90 91 // The database connection to use 92 DB *MailboxDatabase 93 94 // Our configuration from the ini 95 Cfg MailboxConfig 96 97 // Fixed size of message bodies 98 MessageBodyBytes int64 `json:"message_body_bytes"` 99 100 // Merchant object 101 Merchant merchant.Merchant 102 103 // Base URL 104 BaseURL string 105 106 // Registration fee for each validity month mailbox 107 MonthlyFee *talerutil.Amount 108 109 // Registration fee for registering or modifying mailbox 110 RegistrationUpdateFee *talerutil.Amount 111 112 // Message fee for receiving a message 113 MessageFee *talerutil.Amount 114 115 // The free message quota 116 FreeMessageQuota uint64 117 118 // How many messages will a single response 119 // contain at maximum. 120 MessageResponseLimit uint64 121 122 // Logger 123 Logger *log.Logger 124 125 // Currency Spec 126 CurrencySpec talerutil.CurrencySpecification 127 } 128 129 type RelativeTime struct { 130 Microseconds uint64 `json:"d_us"` 131 } 132 133 // 1 Month as Go duration 134 const monthDuration = time.Hour * 24 * 30 135 136 // VersionResponse is the JSON response of the /config endpoint 137 type VersionResponse struct { 138 // libtool-style representation of the Mailbox protocol version, see 139 // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning 140 // The format is "current:revision:age". 141 Version string `json:"version"` 142 143 // Name of the protocol. 144 Name string `json:"name"` // "taler-mailbox" 145 146 // Fixed size of message bodies 147 MessageBodyBytes int64 `json:"message_body_bytes"` 148 149 // How long will the service store a message 150 // before giving up 151 DeliveryPeriod RelativeTime `json:"delivery_period" gorm:"embedded;embeddedPrefix:delivery_period_"` 152 153 // How many messages will a single response 154 // contain at maximum. 155 MessageResponseLimit uint64 `json:"message_response_limit"` 156 157 // How much is the cost of a single 158 // registration (update) of a mailbox 159 // May be 0 for a free update/registration. 160 RegistrationUpdateFee string `json:"registration_update_fee"` 161 162 // How much is the cost of a single 163 // registration period (30 days) of a mailbox 164 // May be 0 for a free registration. 165 MonthlyFee string `json:"monthly_fee"` 166 167 // How much is the cost to send a single 168 // message to a mailbox. 169 // May be 0 for free message sending. 170 MessageFee string `json:"message_fee"` 171 172 // How many messages can be send and 173 // are stored by the service for free. 174 // After the quota is reached, the 175 // regular message_fee applies. 176 // May be 0 for no free quota. 177 FreeMessageQuota string `json:"free_message_quota"` 178 } 179 180 type MailboxRegistrationRequest struct { 181 182 // Keys to add/update for a mailbox. 183 MailboxMetadata MailboxMetadata `json:"mailbox_metadata"` 184 185 // Signature by the mailbox's signing key affirming 186 // the update of keys, of purpose 187 // TALER_SIGNATURE_WALLET_MAILBOX_KEYS_UPDATE. 188 // The signature is created over the SHA-512 hash 189 // of (encryptionKeyType||encryptionKey||expiration) 190 Signature string `json:"signature"` 191 } 192 193 // MailboxRateLimitedResponse is the JSON response when a rate limit is hit 194 type MailboxRateLimitedResponse struct { 195 196 // Taler error code, TALER_EC_mailbox_REGISTER_RATE_LIMITED. 197 Code int `json:"code"` 198 199 // When the client should retry. Currently: In microseconds 200 RetryDelay int64 `json:"retry_delay"` 201 202 // The human readable error message. 203 Hint string `json:"hint"` 204 } 205 206 func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { 207 dp, err := m.Cfg.Ini.GetDuration("mailbox", "delivery_period", 3*24*time.Hour) 208 if err != nil { 209 log.Fatal(err) 210 } 211 cfg := VersionResponse{ 212 Version: m.Cfg.LibtoolVersion, 213 Name: "taler-mailbox", 214 MessageBodyBytes: m.MessageBodyBytes, 215 MessageResponseLimit: m.MessageResponseLimit, 216 MonthlyFee: m.MonthlyFee.String(), 217 RegistrationUpdateFee: m.RegistrationUpdateFee.String(), 218 DeliveryPeriod: RelativeTime{Microseconds: uint64(dp.Microseconds())}, 219 } 220 w.Header().Set("Content-Type", "application/json") 221 response, _ := json.Marshal(cfg) 222 w.Write(response) 223 } 224 225 func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { 226 vars := mux.Vars(r) 227 //to, toSet := vars["timeout_ms"] 228 var entries []InboxEntry 229 // FIXME rate limit 230 // FIXME timeout 231 // FIXME possibly limit results here 232 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 233 entries, err := m.DB.GetMessages(vars["h_mailbox"], int(m.MessageResponseLimit)) 234 if err != nil { 235 m.Logf(LogError, "Error getting messages: %v", err) 236 w.WriteHeader(http.StatusNotFound) 237 return 238 } 239 if len(entries) == 0 { 240 w.WriteHeader(http.StatusNoContent) 241 return 242 } 243 // Add ETag of first message ID 244 etag := entries[0].Serial 245 w.Header().Add("ETag", fmt.Sprintf("%d", etag)) 246 for _, entry := range entries { 247 w.Write(entry.Body) 248 } 249 } 250 251 func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { 252 vars := mux.Vars(r) 253 var entry InboxEntry 254 if r.Body == nil { 255 http.Error(w, "No request body", http.StatusBadRequest) 256 return 257 } 258 if r.ContentLength != m.MessageBodyBytes { 259 http.Error(w, "Wrong message size", http.StatusBadRequest) 260 return 261 } 262 body, err := io.ReadAll(r.Body) 263 if err != nil { 264 log.Printf("%v", err) 265 http.Error(w, "Cannot read body", http.StatusBadRequest) 266 return 267 } 268 if !m.MessageFee.IsZero() { 269 var count int64 270 count, err = m.DB.GetMessagesCount(vars["h_mailbox"]) 271 if nil != err { 272 m.Logf(LogError, "Error getting messages: %v", err) 273 http.Error(w, "Cannot look for entries", http.StatusBadRequest) 274 return 275 } 276 if count >= int64(m.FreeMessageQuota) { 277 w.WriteHeader(http.StatusPaymentRequired) 278 //w.Header().Set("Taler", payto) FIXME generate payto 279 return 280 } 281 } 282 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 283 err = m.DB.GetInboxEntryBySigningKeyAndBody(&entry, vars["h_mailbox"], body) 284 if err == nil { 285 w.WriteHeader(http.StatusNotModified) 286 return 287 } 288 entry.HashedSigningKey = vars["h_mailbox"] 289 entry.Body = body 290 err = m.DB.InsertInboxEntry(&entry) 291 if err != nil { 292 m.Logf(LogError, "Error storing message: %v", err) 293 w.WriteHeader(http.StatusInternalServerError) 294 return 295 } 296 w.WriteHeader(http.StatusNoContent) 297 } 298 299 func (m *Mailbox) getKeysResponse(w http.ResponseWriter, r *http.Request) { 300 vars := mux.Vars(r) 301 var keyEntry MailboxMetadata 302 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 303 err := m.DB.GetMailboxMetadataBySigningKey(&keyEntry, vars["h_mailbox"]) 304 if err != nil { 305 m.Logf(LogError, "Error finding mailbox: %v", err) 306 w.WriteHeader(http.StatusNotFound) 307 return 308 } 309 m.Logf(LogDebug, "entry expires at %d, have %d", keyEntry.Expiration, time.Now().Unix()) 310 if int64(keyEntry.Expiration.Seconds) < int64(time.Now().Unix()) { 311 w.WriteHeader(http.StatusNotFound) 312 return 313 } 314 response, _ := json.Marshal(keyEntry) 315 w.Write(response) 316 } 317 318 func (m *Mailbox) validateRegistrationSignature(msg MailboxRegistrationRequest) error { 319 var expNbo [8]byte 320 var signedMsg [72]byte 321 encPk, err := util.Base32CrockfordDecode(msg.MailboxMetadata.EncryptionKey, 32) 322 if err != nil { 323 return fmt.Errorf("unable to decode encryption key") 324 } 325 pkey, err := util.Base32CrockfordDecode(msg.MailboxMetadata.SigningKey, 32) 326 if err != nil { 327 return fmt.Errorf("unable to decode pubkey") 328 } 329 pk := ed25519.PublicKey(pkey) 330 sig, err := util.Base32CrockfordDecode(msg.Signature, 64) 331 if nil != err { 332 return fmt.Errorf("unable to decode signature") 333 } 334 binary.BigEndian.PutUint64(expNbo[:], msg.MailboxMetadata.Expiration.Seconds) 335 size := signedMsg[0:4] 336 binary.BigEndian.PutUint32(size, 64+4+4) 337 purp := signedMsg[4:8] 338 binary.BigEndian.PutUint32(purp, gana.TalerSignaturePurposeMailboxRegister) 339 h := sha512.New() 340 h.Write([]byte(msg.MailboxMetadata.EncryptionKeyType)) // Currently always X25519 341 h.Write(encPk) 342 h.Write(expNbo[:]) 343 copy(signedMsg[8:], h.Sum(nil)) 344 if !ed25519.Verify(pk, signedMsg[0:], sig) { 345 return fmt.Errorf("signature invalid") 346 } 347 return nil 348 } 349 350 // Check if this is a non-zero, positive amount 351 func calculateCost(sliceCostAmount string, fixedCostAmount string, howLong time.Duration, sliceDuration time.Duration) (*talerutil.Amount, error) { 352 sliceCount := int(float64(howLong.Microseconds()) / float64(sliceDuration.Microseconds())) 353 sliceCost, err := talerutil.ParseAmount(sliceCostAmount) 354 if nil != err { 355 return nil, err 356 } 357 fixedCost, err := talerutil.ParseAmount(fixedCostAmount) 358 if nil != err { 359 return nil, err 360 } 361 sum := &talerutil.Amount{ 362 Currency: sliceCost.Currency, 363 Value: 0, 364 Fraction: 0, 365 } 366 for range sliceCount { 367 sum, err = sum.Add(*sliceCost) 368 if nil != err { 369 return nil, err 370 } 371 } 372 sum, err = sum.Add(*fixedCost) 373 if nil != err { 374 return nil, err 375 } 376 return sum, nil 377 } 378 379 func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request) { 380 var msg MailboxRegistrationRequest 381 var pendingRegistration PendingMailboxRegistration 382 var registrationEntry MailboxMetadata 383 if r.Body == nil { 384 m.Logf(LogError, "no request body") 385 http.Error(w, "No request body", http.StatusBadRequest) 386 return 387 } 388 err := json.NewDecoder(r.Body).Decode(&msg) 389 if err != nil { 390 http.Error(w, "Malformed request body", http.StatusBadRequest) 391 return 392 } 393 pkey, err := util.Base32CrockfordDecode(msg.MailboxMetadata.SigningKey, 32) 394 if err != nil { 395 http.Error(w, "Public key invalid", http.StatusBadRequest) 396 return 397 } 398 pk := ed25519.PublicKey(pkey) 399 err = m.validateRegistrationSignature(msg) 400 if nil != err { 401 http.Error(w, "Signature verification failed", http.StatusBadRequest) 402 return 403 } 404 h := sha512.New() 405 h.Write(pkey) 406 hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) 407 pendingRegistration.HashedSigningKey = hMailbox 408 // Round to the nearest multiple of a month 409 reqExpiration := time.Unix(int64(msg.MailboxMetadata.Expiration.Seconds), 0) 410 now := time.Now() 411 reqDuration := reqExpiration.Sub(now).Round(monthDuration) 412 err = m.DB.GetMailboxMetadataBySigningKey(®istrationEntry, hMailbox) 413 if err == nil { 414 // This probably means the registration is modified or extended or both 415 entryModified := (registrationEntry.EncryptionKey != msg.MailboxMetadata.EncryptionKey) 416 // At least one MonthlyFee 417 if reqDuration.Microseconds() == 0 && !entryModified { 418 // Nothing changed. Return validity 419 w.WriteHeader(http.StatusNotModified) 420 return 421 } 422 } else { 423 // Entry does not yet exist, add but immediately expire it 424 registrationEntry = msg.MailboxMetadata 425 registrationEntry.Expiration.Seconds = uint64(time.Now().Unix() - 1) 426 hAddr := sha512.New() 427 hAddr.Write(pk) 428 registrationEntry.HashedSigningKey = util.Base32CrockfordEncode(hAddr.Sum(nil)) 429 err = m.DB.InsertMailboxRegistration(®istrationEntry) 430 if nil != err { 431 m.Logf(LogError, "%v\n", err) 432 w.WriteHeader(http.StatusInternalServerError) 433 return 434 } 435 } 436 err = m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingRegistration, hMailbox) 437 pendingRegistrationExists := (nil == err) 438 if !pendingRegistrationExists { 439 pendingRegistration.HashedSigningKey = hMailbox 440 pendingRegistration.Duration = reqDuration.Microseconds() 441 err = m.DB.InsertPendingMailboxRegistration(&pendingRegistration) 442 if nil != err { 443 m.Logf(LogError, "Error inserting pending registration: %v\n", err) 444 w.WriteHeader(http.StatusInternalServerError) 445 return 446 } 447 err = m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingRegistration, hMailbox) 448 if nil != err { 449 m.Logf(LogError, "Error getting pending registration: %v\n", err) 450 w.WriteHeader(http.StatusInternalServerError) 451 return 452 } 453 } 454 // At least the update fee needs to be paid 455 cost, err := calculateCost(m.MonthlyFee.String(), 456 m.RegistrationUpdateFee.String(), 457 reqDuration, 458 monthDuration) 459 if err != nil { 460 m.Logf(LogError, "Error calculating cost: %v\n", err) 461 w.WriteHeader(http.StatusInternalServerError) 462 return 463 } 464 if !cost.IsZero() { 465 if len(pendingRegistration.OrderID) == 0 { 466 // Add new order 467 orderID, newOrderErr := m.Merchant.AddNewOrder(*cost, "Mailbox registration", m.BaseURL) 468 if newOrderErr != nil { 469 m.Logf(LogError, "Error adding order: %v", newOrderErr) 470 w.WriteHeader(http.StatusInternalServerError) 471 return 472 } 473 m.Logf(LogDebug, "New order ID %s for pending registration for %s", orderID, pendingRegistration.HashedSigningKey) 474 pendingRegistration.OrderID = orderID 475 } 476 // Check if order paid. 477 // FIXME: Remember that it was activated and paid 478 // FIXME: We probably need to handle the return code here (see gns registrar for how) 479 _, _, payto, paytoErr := m.Merchant.IsOrderPaid(pendingRegistration.OrderID) 480 if paytoErr != nil { 481 fmt.Println(paytoErr) 482 w.WriteHeader(http.StatusInternalServerError) 483 m.Logf(LogError, "Error checking if order is paid: %s\n", paytoErr.Error()) 484 return 485 } 486 if len(payto) != 0 { 487 err = m.DB.UpdatePendingMailboxRegistrationOrderId(&pendingRegistration) 488 if err != nil { 489 m.Logf(LogError, "Error updating pending registration: %v\n", err) 490 w.WriteHeader(http.StatusInternalServerError) 491 return 492 } 493 w.WriteHeader(http.StatusPaymentRequired) 494 w.Header().Set("Taler", payto) 495 return 496 } 497 } 498 // Update expiration time of registration. 499 registrationEntry.Expiration.Seconds += uint64(reqDuration.Seconds()) 500 _, err = m.DB.DeletePendingRegistration(&pendingRegistration) 501 if nil != err { 502 m.Logf(LogError, "Error deleting pending registration: %v\n", err) 503 w.WriteHeader(http.StatusInternalServerError) 504 return 505 } 506 err = m.DB.UpdateMailboxExpiration(®istrationEntry) 507 if nil != err { 508 m.Logf(LogError, "Error updating mailbox registration: %v\n", err) 509 w.WriteHeader(http.StatusInternalServerError) 510 return 511 } 512 w.WriteHeader(http.StatusNoContent) 513 } 514 515 func (m *Mailbox) checkPendingMailboxRegistrationUpdates(hMailbox string) { 516 var pendingEntry PendingMailboxRegistration 517 var registrationEntry MailboxMetadata 518 err := m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingEntry, hMailbox) 519 if err != nil { 520 return 521 } 522 m.Logf(LogDebug, "Found pending registration for %s, OrderID: %s", hMailbox, pendingEntry.OrderID) 523 rc, orderStatus, _, paytoErr := m.Merchant.IsOrderPaid(pendingEntry.OrderID) 524 if nil != paytoErr { 525 if rc == http.StatusNotFound { 526 m.Logf(LogInfo, "Registration order for `%s' not found, removing\n", hMailbox) 527 } 528 _, err = m.DB.DeletePendingRegistration(&pendingEntry) 529 if nil != err { 530 m.Logf(LogInfo, "Error deleting pending registration: %v\n", err) 531 } 532 return 533 } 534 m.Logf(LogDebug, "Order status for %s is %s", pendingEntry.HashedSigningKey, orderStatus) 535 if merchant.OrderPaid == orderStatus { 536 m.Logf(LogDebug, "Order for %v appears to be paid", pendingEntry) 537 err = m.DB.GetMailboxMetadataBySigningKey(®istrationEntry, hMailbox) 538 if err == nil { 539 m.Logf(LogDebug, "Adding %d seconds to entry expiration", pendingEntry.Duration) 540 registrationEntry.Expiration.Seconds += uint64(pendingEntry.Duration) 541 err = m.DB.UpdateMailboxExpiration(®istrationEntry) 542 if nil != err { 543 m.Logf(LogInfo, "Error updating mailbox expiration: %v\n", err) 544 } 545 _, err = m.DB.DeletePendingRegistration(&pendingEntry) 546 if nil != err { 547 m.Logf(LogInfo, "Error deleting pending registration: %v\n", err) 548 } 549 } 550 return 551 } 552 } 553 554 func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) { 555 vars := mux.Vars(r) 556 etagHeader := r.Header.Get("If-Match") 557 if etagHeader == "" { 558 http.Error(w, "If-Match header missing", 400) 559 return 560 } 561 if strings.Contains(etagHeader, ",") { 562 http.Error(w, "If-Match contains multiple values", 400) 563 return 564 } 565 expectedETag, err := strconv.Atoi(etagHeader) 566 if err != nil { 567 http.Error(w, "If-Match contains malformed etag number", 400) 568 return 569 } 570 pkey, err := util.Base32CrockfordDecode(vars["mailbox"], 32) 571 if err != nil { 572 w.WriteHeader(http.StatusBadRequest) 573 return 574 } 575 count := 1 576 countStr := r.URL.Query().Get("count") 577 if len(countStr) > 0 { 578 count, err = strconv.Atoi(countStr) 579 if err != nil { 580 http.Error(w, "Malformed count parameter", http.StatusBadRequest) 581 w.WriteHeader(http.StatusBadRequest) 582 return 583 } 584 } 585 headerSig := r.Header["Taler-Mailbox-Delete-Signature"] 586 if nil == headerSig { 587 http.Error(w, "Missing signature", http.StatusBadRequest) 588 return 589 } 590 pk := ed25519.PublicKey(pkey) 591 sig, err := util.Base32CrockfordDecode(headerSig[0], 64) 592 if nil != err { 593 w.WriteHeader(http.StatusBadRequest) 594 return 595 } 596 h := sha512.New() 597 h.Write(pkey) 598 hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) 599 m.checkPendingMailboxRegistrationUpdates(hMailbox) 600 var signedMsg [4 * 4]byte 601 binary.BigEndian.PutUint32(signedMsg[0:4], 4*4) 602 binary.BigEndian.PutUint32(signedMsg[4:8], gana.TalerSignaturePurposeMailboxMessagesDelete) 603 binary.BigEndian.PutUint32(signedMsg[8:12], uint32(expectedETag)) 604 binary.BigEndian.PutUint32(signedMsg[12:16], uint32(count)) 605 if !ed25519.Verify(pk, signedMsg[0:], sig) { 606 w.WriteHeader(http.StatusForbidden) 607 return 608 } 609 // Check that expectedETag actually exists 610 var entry InboxEntry 611 err = m.DB.GetInboxEntryBySerial(&entry, hMailbox, int64(expectedETag)) 612 if err != nil { 613 m.Logf(LogDebug, "Message to delete not found with ID %d", expectedETag) 614 w.WriteHeader(http.StatusNotFound) 615 return 616 } 617 m.Logf(LogError, "Deleting from entry %v up to %d messages\n", entry, count) 618 num, err := m.DB.DeleteInboxEntryBySerial(&entry, count) 619 if err != nil { 620 m.Logf(LogDebug, "Failed to delete messages: %v", err) 621 w.WriteHeader(http.StatusInternalServerError) 622 return 623 } 624 m.Logf(LogDebug, "Found matching ID (serial: %d), deleted %d messages", entry.Serial, num) 625 w.WriteHeader(http.StatusNoContent) 626 } 627 628 func (m *Mailbox) termsResponse(w http.ResponseWriter, r *http.Request) { 629 termspath := m.Cfg.Ini.GetFilename("mailbox", "default_terms_path", "terms/", m.Cfg.Datahome) 630 tos.ServiceTermsResponse(w, r, termspath, tos.TalerTosConfig{ 631 DefaultFileType: m.Cfg.Ini.GetString("mailbox", "default_doc_filetype", "text/html"), 632 DefaultLanguage: m.Cfg.Ini.GetString("mailbox", "default_doc_lang", "en"), 633 SupportedFileTypes: strings.Split(m.Cfg.Ini.GetString("mailbox", "supported_doc_filetypes", ""), " "), 634 }) 635 } 636 637 func (m *Mailbox) privacyResponse(w http.ResponseWriter, r *http.Request) { 638 pppath := m.Cfg.Ini.GetFilename("mailbox", "default_pp_path", "privacy/", m.Cfg.Datahome) 639 tos.PrivacyPolicyResponse(w, r, pppath, tos.TalerTosConfig{ 640 DefaultFileType: m.Cfg.Ini.GetString("mailbox", "default_doc_filetype", "text/html"), 641 DefaultLanguage: m.Cfg.Ini.GetString("mailbox", "default_doc_lang", "en"), 642 SupportedFileTypes: strings.Split(m.Cfg.Ini.GetString("mailbox", "supported_doc_filetypes", ""), " "), 643 }) 644 } 645 646 func (m *Mailbox) setupHandlers() { 647 m.Router = mux.NewRouter().StrictSlash(true) 648 649 /* ToS API */ 650 m.Router.HandleFunc("/terms", m.termsResponse).Methods("GET") 651 m.Router.HandleFunc("/privacy", m.privacyResponse).Methods("GET") 652 653 /* Config API */ 654 m.Router.HandleFunc("/config", m.configResponse).Methods("GET") 655 656 /* Mailbox API */ 657 m.Router.HandleFunc("/register", m.registerMailboxResponse).Methods("POST") 658 m.Router.HandleFunc("/info/{h_mailbox}", m.getKeysResponse).Methods("GET") 659 m.Router.HandleFunc("/{h_mailbox}", m.sendMessageResponse).Methods("POST") 660 m.Router.HandleFunc("/{h_mailbox}", m.getMessagesResponse).Methods("GET") 661 m.Router.HandleFunc("/{mailbox}", m.deleteMessagesResponse).Methods("DELETE") 662 } 663 664 func (m *Mailbox) Logf(loglevel LogLevel, fmt string, args ...any) { 665 if loglevel < m.Cfg.Loglevel { 666 return 667 } 668 m.Logger.SetPrefix("taler-mailbox - " + LoglevelStringMap[loglevel] + " ") 669 m.Logger.Printf(fmt, args...) 670 } 671 672 // Initialize the Mailbox instance with cfgfile 673 func (m *Mailbox) Initialize(cfg MailboxConfig) { 674 m.Cfg = cfg 675 m.Logger = log.New(os.Stdout, "taler-mailbox:", log.LstdFlags) 676 m.BaseURL = cfg.Ini.GetString("mailbox", "base_url", "https://example.com") 677 m.MessageBodyBytes = cfg.Ini.GetInt64("mailbox", "message_body_bytes", 256) 678 m.MessageResponseLimit = uint64(cfg.Ini.GetInt64("mailbox", "message_response_limit", 50)) 679 monthlyFee, err := cfg.Ini.GetAmount("mailbox", "monthly_fee", &talerutil.Amount{}) 680 if err != nil { 681 fmt.Printf("Failed to parse monthly fee: %v", err) 682 os.Exit(1) 683 } 684 m.MonthlyFee = monthlyFee 685 updateFee, err := cfg.Ini.GetAmount("mailbox", "registration_update_fee", &talerutil.Amount{}) 686 if err != nil { 687 fmt.Printf("Failed to parse update fee: %v", err) 688 os.Exit(1) 689 } 690 m.RegistrationUpdateFee = updateFee 691 messageFee, err := cfg.Ini.GetAmount("mailbox", "message_fee", &talerutil.Amount{}) 692 if err != nil { 693 fmt.Printf("Failed to parse message fee: %v", err) 694 os.Exit(1) 695 } 696 m.MessageFee = messageFee 697 m.FreeMessageQuota = uint64(cfg.Ini.GetInt64("mailbox", "free_message_quota", 0)) 698 m.DB = cfg.DB 699 go func() { 700 for { 701 num, err := m.DB.DeleteStaleRegistrations(time.Now()) 702 if err != nil { 703 m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) 704 } 705 m.Logf(LogInfo, "Cleaned up %d stale registrations.\n", num) 706 time.Sleep(time.Hour * 24) 707 } 708 }() 709 // Clean up pending 710 pendingExp, err := cfg.Ini.GetDuration("mailbox", "pending_registration_expiration", 24*time.Hour) 711 if err != nil { 712 fmt.Printf("Failed to parse pending registration expiration: %v", err) 713 os.Exit(1) 714 } 715 go func() { 716 for { 717 num, err := m.DB.DeleteStalePendingRegistrations(time.Now().Add(-pendingExp)) 718 if err != nil { 719 m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) 720 } 721 m.Logf(LogInfo, "Cleaned up %d stale pending registrations.\n", num) 722 time.Sleep(pendingExp) 723 } 724 }() 725 726 m.Merchant = cfg.Merchant 727 if !monthlyFee.IsZero() { 728 merchConfig, err := m.Merchant.GetConfig() 729 if err != nil { 730 fmt.Printf("Failed to get merchant config: %v", err) 731 os.Exit(1) 732 } 733 currencySpec, currencySupported := merchConfig.Currencies[monthlyFee.Currency] 734 for !currencySupported { 735 fmt.Printf("Currency `%s' not supported by merchant!\n", monthlyFee.Currency) 736 os.Exit(1) 737 } 738 m.CurrencySpec = currencySpec 739 } 740 m.setupHandlers() 741 }