mailbox.go (27836B)
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 // configResponse returns the service configuration. 207 // 208 // @Summary Get service configuration 209 // @Description Returns service metadata including fees, message size limits, and delivery period. 210 // @Tags config 211 // @Produce json 212 // @Success 200 {object} VersionResponse 213 // @Router /config [get] 214 func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { 215 dp, err := m.Cfg.Ini.GetDuration("mailbox", "delivery_period", 3*24*time.Hour) 216 if err != nil { 217 log.Fatal(err) 218 } 219 cfg := VersionResponse{ 220 Version: m.Cfg.LibtoolVersion, 221 Name: "taler-mailbox", 222 MessageBodyBytes: m.MessageBodyBytes, 223 MessageResponseLimit: m.MessageResponseLimit, 224 MonthlyFee: m.MonthlyFee.String(), 225 RegistrationUpdateFee: m.RegistrationUpdateFee.String(), 226 DeliveryPeriod: RelativeTime{Microseconds: uint64(dp.Microseconds())}, 227 } 228 w.Header().Set("Content-Type", "application/json") 229 response, _ := json.Marshal(cfg) 230 w.Write(response) 231 } 232 233 // getMessagesResponse retrieves pending messages for a mailbox. 234 // 235 // @Summary Retrieve messages 236 // @Description Returns up to MessageResponseLimit encrypted message bodies for the given mailbox. 237 // @Description The ETag response header contains the serial number of the first message. 238 // @Tags mailbox 239 // @Produce application/octet-stream 240 // @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)" 241 // @Success 200 "One or more message bodies concatenated" 242 // @Success 204 "No messages available" 243 // @Failure 404 "Mailbox not found" 244 // @Router /{h_mailbox} [get] 245 func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { 246 vars := mux.Vars(r) 247 //to, toSet := vars["timeout_ms"] 248 var entries []InboxEntry 249 // FIXME rate limit 250 // FIXME timeout 251 // FIXME possibly limit results here 252 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 253 entries, err := m.DB.GetMessages(vars["h_mailbox"], int(m.MessageResponseLimit)) 254 if err != nil { 255 m.Logf(LogError, "Error getting messages: %v", err) 256 w.WriteHeader(http.StatusNotFound) 257 return 258 } 259 if len(entries) == 0 { 260 w.WriteHeader(http.StatusNoContent) 261 return 262 } 263 // Add ETag of first message ID 264 etag := entries[0].Serial 265 w.Header().Add("ETag", fmt.Sprintf("%d", etag)) 266 for _, entry := range entries { 267 w.Write(entry.Body) 268 } 269 } 270 271 // sendMessageResponse delivers a message to a mailbox. 272 // 273 // @Summary Send a message 274 // @Description Stores an encrypted message body for the given mailbox. The body must be 275 // @Description exactly MessageBodyBytes in size. 276 // @Tags mailbox 277 // @Accept application/octet-stream 278 // @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)" 279 // @Param body body string true "Encrypted message body (fixed size)" 280 // @Success 204 "Message stored" 281 // @Success 304 "Identical message already stored" 282 // @Failure 400 "Missing or wrong-size body" 283 // @Failure 402 "Payment required (free quota exceeded)" 284 // @Failure 500 285 // @Router /{h_mailbox} [post] 286 func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { 287 vars := mux.Vars(r) 288 var entry InboxEntry 289 if r.Body == nil { 290 http.Error(w, "No request body", http.StatusBadRequest) 291 return 292 } 293 if r.ContentLength != m.MessageBodyBytes { 294 http.Error(w, "Wrong message size", http.StatusBadRequest) 295 return 296 } 297 body, err := io.ReadAll(r.Body) 298 if err != nil { 299 log.Printf("%v", err) 300 http.Error(w, "Cannot read body", http.StatusBadRequest) 301 return 302 } 303 if !m.MessageFee.IsZero() { 304 var count int64 305 count, err = m.DB.GetMessagesCount(vars["h_mailbox"]) 306 if nil != err { 307 m.Logf(LogError, "Error getting messages: %v", err) 308 http.Error(w, "Cannot look for entries", http.StatusBadRequest) 309 return 310 } 311 if count >= int64(m.FreeMessageQuota) { 312 w.WriteHeader(http.StatusPaymentRequired) 313 //w.Header().Set("Taler", payto) FIXME generate payto 314 return 315 } 316 } 317 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 318 err = m.DB.GetInboxEntryBySigningKeyAndBody(&entry, vars["h_mailbox"], body) 319 if err == nil { 320 w.WriteHeader(http.StatusNotModified) 321 return 322 } 323 entry.HashedSigningKey = vars["h_mailbox"] 324 entry.Body = body 325 err = m.DB.InsertInboxEntry(&entry) 326 if err != nil { 327 m.Logf(LogError, "Error storing message: %v", err) 328 w.WriteHeader(http.StatusInternalServerError) 329 return 330 } 331 w.WriteHeader(http.StatusNoContent) 332 } 333 334 // getKeysResponse returns the public key metadata for a registered mailbox. 335 // 336 // @Summary Get mailbox info 337 // @Description Returns the signing and encryption key metadata for the given mailbox. 338 // @Tags mailbox 339 // @Produce json 340 // @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)" 341 // @Success 200 {object} MailboxMetadata 342 // @Failure 404 "Mailbox not found or expired" 343 // @Router /info/{h_mailbox} [get] 344 func (m *Mailbox) getKeysResponse(w http.ResponseWriter, r *http.Request) { 345 vars := mux.Vars(r) 346 var keyEntry MailboxMetadata 347 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 348 err := m.DB.GetMailboxMetadataBySigningKey(&keyEntry, vars["h_mailbox"]) 349 if err != nil { 350 m.Logf(LogError, "Error finding mailbox: %v", err) 351 w.WriteHeader(http.StatusNotFound) 352 return 353 } 354 m.Logf(LogDebug, "entry expires at %d, have %d", keyEntry.Expiration, time.Now().Unix()) 355 if int64(keyEntry.Expiration.Seconds) < int64(time.Now().Unix()) { 356 w.WriteHeader(http.StatusNotFound) 357 return 358 } 359 response, _ := json.Marshal(keyEntry) 360 w.Write(response) 361 } 362 363 func (m *Mailbox) validateRegistrationSignature(msg MailboxRegistrationRequest) error { 364 var expNbo [8]byte 365 var signedMsg [72]byte 366 encPk, err := util.Base32CrockfordDecode(msg.MailboxMetadata.EncryptionKey, 32) 367 if err != nil { 368 return fmt.Errorf("unable to decode encryption key") 369 } 370 pkey, err := util.Base32CrockfordDecode(msg.MailboxMetadata.SigningKey, 32) 371 if err != nil { 372 return fmt.Errorf("unable to decode pubkey") 373 } 374 pk := ed25519.PublicKey(pkey) 375 sig, err := util.Base32CrockfordDecode(msg.Signature, 64) 376 if nil != err { 377 return fmt.Errorf("unable to decode signature") 378 } 379 binary.BigEndian.PutUint64(expNbo[:], msg.MailboxMetadata.Expiration.Seconds) 380 size := signedMsg[0:4] 381 binary.BigEndian.PutUint32(size, 64+4+4) 382 purp := signedMsg[4:8] 383 binary.BigEndian.PutUint32(purp, gana.TalerSignaturePurposeMailboxRegister) 384 h := sha512.New() 385 h.Write([]byte(msg.MailboxMetadata.EncryptionKeyType)) // Currently always X25519 386 h.Write(encPk) 387 h.Write(expNbo[:]) 388 copy(signedMsg[8:], h.Sum(nil)) 389 if !ed25519.Verify(pk, signedMsg[0:], sig) { 390 return fmt.Errorf("signature invalid") 391 } 392 return nil 393 } 394 395 // Check if this is a non-zero, positive amount 396 func calculateCost(sliceCostAmount string, fixedCostAmount string, howLong time.Duration, sliceDuration time.Duration) (*talerutil.Amount, error) { 397 sliceCount := int(float64(howLong.Microseconds()) / float64(sliceDuration.Microseconds())) 398 sliceCost, err := talerutil.ParseAmount(sliceCostAmount) 399 if nil != err { 400 return nil, err 401 } 402 fixedCost, err := talerutil.ParseAmount(fixedCostAmount) 403 if nil != err { 404 return nil, err 405 } 406 sum := &talerutil.Amount{ 407 Currency: sliceCost.Currency, 408 Value: 0, 409 Fraction: 0, 410 } 411 for range sliceCount { 412 sum, err = sum.Add(*sliceCost) 413 if nil != err { 414 return nil, err 415 } 416 } 417 sum, err = sum.Add(*fixedCost) 418 if nil != err { 419 return nil, err 420 } 421 return sum, nil 422 } 423 424 // registerMailboxResponse registers or updates a mailbox. 425 // 426 // @Summary Register or update mailbox 427 // @Description Registers a new mailbox or updates the keys/expiration of an existing one. 428 // @Description A valid EdDSA signature over the key material must be provided. 429 // @Tags mailbox 430 // @Accept json 431 // @Param body body MailboxRegistrationRequest true "Registration request" 432 // @Success 204 "Registration confirmed" 433 // @Success 304 "Nothing changed" 434 // @Failure 400 "Invalid request body or signature" 435 // @Failure 402 "Payment required" 436 // @Failure 500 437 // @Router /register [post] 438 func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request) { 439 var msg MailboxRegistrationRequest 440 var pendingRegistration PendingMailboxRegistration 441 var registrationEntry MailboxMetadata 442 if r.Body == nil { 443 m.Logf(LogError, "no request body") 444 http.Error(w, "No request body", http.StatusBadRequest) 445 return 446 } 447 err := json.NewDecoder(r.Body).Decode(&msg) 448 if err != nil { 449 http.Error(w, "Malformed request body", http.StatusBadRequest) 450 return 451 } 452 pkey, err := util.Base32CrockfordDecode(msg.MailboxMetadata.SigningKey, 32) 453 if err != nil { 454 http.Error(w, "Public key invalid", http.StatusBadRequest) 455 return 456 } 457 pk := ed25519.PublicKey(pkey) 458 err = m.validateRegistrationSignature(msg) 459 if nil != err { 460 http.Error(w, "Signature verification failed", http.StatusBadRequest) 461 return 462 } 463 h := sha512.New() 464 h.Write(pkey) 465 hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) 466 pendingRegistration.HashedSigningKey = hMailbox 467 // Round to the nearest multiple of a month 468 reqExpiration := time.Unix(int64(msg.MailboxMetadata.Expiration.Seconds), 0) 469 now := time.Now() 470 reqDuration := reqExpiration.Sub(now).Round(monthDuration) 471 err = m.DB.GetMailboxMetadataBySigningKey(®istrationEntry, hMailbox) 472 if err == nil { 473 // This probably means the registration is modified or extended or both 474 entryModified := (registrationEntry.EncryptionKey != msg.MailboxMetadata.EncryptionKey) 475 // At least one MonthlyFee 476 if reqDuration.Microseconds() == 0 && !entryModified { 477 // Nothing changed. Return validity 478 w.WriteHeader(http.StatusNotModified) 479 return 480 } 481 } else { 482 // Entry does not yet exist, add but immediately expire it 483 registrationEntry = msg.MailboxMetadata 484 registrationEntry.Expiration.Seconds = uint64(time.Now().Unix() - 1) 485 hAddr := sha512.New() 486 hAddr.Write(pk) 487 registrationEntry.HashedSigningKey = util.Base32CrockfordEncode(hAddr.Sum(nil)) 488 err = m.DB.InsertMailboxRegistration(®istrationEntry) 489 if nil != err { 490 m.Logf(LogError, "%v\n", err) 491 w.WriteHeader(http.StatusInternalServerError) 492 return 493 } 494 } 495 err = m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingRegistration, hMailbox) 496 pendingRegistrationExists := (nil == err) 497 if !pendingRegistrationExists { 498 pendingRegistration.HashedSigningKey = hMailbox 499 pendingRegistration.Duration = reqDuration.Microseconds() 500 err = m.DB.InsertPendingMailboxRegistration(&pendingRegistration) 501 if nil != err { 502 m.Logf(LogError, "Error inserting pending registration: %v\n", err) 503 w.WriteHeader(http.StatusInternalServerError) 504 return 505 } 506 err = m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingRegistration, hMailbox) 507 if nil != err { 508 m.Logf(LogError, "Error getting pending registration: %v\n", err) 509 w.WriteHeader(http.StatusInternalServerError) 510 return 511 } 512 } 513 // At least the update fee needs to be paid 514 cost, err := calculateCost(m.MonthlyFee.String(), 515 m.RegistrationUpdateFee.String(), 516 reqDuration, 517 monthDuration) 518 if err != nil { 519 m.Logf(LogError, "Error calculating cost: %v\n", err) 520 w.WriteHeader(http.StatusInternalServerError) 521 return 522 } 523 if !cost.IsZero() { 524 if len(pendingRegistration.OrderID) == 0 { 525 // Add new order 526 orderID, newOrderErr := m.Merchant.AddNewOrder(*cost, "Mailbox registration", m.BaseURL) 527 if newOrderErr != nil { 528 m.Logf(LogError, "Error adding order: %v", newOrderErr) 529 w.WriteHeader(http.StatusInternalServerError) 530 return 531 } 532 m.Logf(LogDebug, "New order ID %s for pending registration for %s", orderID, pendingRegistration.HashedSigningKey) 533 pendingRegistration.OrderID = orderID 534 } 535 // Check if order paid. 536 // FIXME: Remember that it was activated and paid 537 // FIXME: We probably need to handle the return code here (see gns registrar for how) 538 _, _, payto, paytoErr := m.Merchant.IsOrderPaid(pendingRegistration.OrderID) 539 if paytoErr != nil { 540 fmt.Println(paytoErr) 541 w.WriteHeader(http.StatusInternalServerError) 542 m.Logf(LogError, "Error checking if order is paid: %s\n", paytoErr.Error()) 543 return 544 } 545 if len(payto) != 0 { 546 err = m.DB.UpdatePendingMailboxRegistrationOrderId(&pendingRegistration) 547 if err != nil { 548 m.Logf(LogError, "Error updating pending registration: %v\n", err) 549 w.WriteHeader(http.StatusInternalServerError) 550 return 551 } 552 w.WriteHeader(http.StatusPaymentRequired) 553 w.Header().Set("Taler", payto) 554 return 555 } 556 } 557 // Update expiration time of registration. 558 registrationEntry.Expiration.Seconds += uint64(reqDuration.Seconds()) 559 _, err = m.DB.DeletePendingRegistration(&pendingRegistration) 560 if nil != err { 561 m.Logf(LogError, "Error deleting pending registration: %v\n", err) 562 w.WriteHeader(http.StatusInternalServerError) 563 return 564 } 565 err = m.DB.UpdateMailboxExpiration(®istrationEntry) 566 if nil != err { 567 m.Logf(LogError, "Error updating mailbox registration: %v\n", err) 568 w.WriteHeader(http.StatusInternalServerError) 569 return 570 } 571 w.WriteHeader(http.StatusNoContent) 572 } 573 574 func (m *Mailbox) checkPendingMailboxRegistrationUpdates(hMailbox string) { 575 var pendingEntry PendingMailboxRegistration 576 var registrationEntry MailboxMetadata 577 err := m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingEntry, hMailbox) 578 if err != nil { 579 return 580 } 581 m.Logf(LogDebug, "Found pending registration for %s, OrderID: %s", hMailbox, pendingEntry.OrderID) 582 rc, orderStatus, _, paytoErr := m.Merchant.IsOrderPaid(pendingEntry.OrderID) 583 if nil != paytoErr { 584 if rc == http.StatusNotFound { 585 m.Logf(LogInfo, "Registration order for `%s' not found, removing\n", hMailbox) 586 } 587 _, err = m.DB.DeletePendingRegistration(&pendingEntry) 588 if nil != err { 589 m.Logf(LogInfo, "Error deleting pending registration: %v\n", err) 590 } 591 return 592 } 593 m.Logf(LogDebug, "Order status for %s is %s", pendingEntry.HashedSigningKey, orderStatus) 594 if merchant.OrderPaid == orderStatus { 595 m.Logf(LogDebug, "Order for %v appears to be paid", pendingEntry) 596 err = m.DB.GetMailboxMetadataBySigningKey(®istrationEntry, hMailbox) 597 if err == nil { 598 m.Logf(LogDebug, "Adding %d seconds to entry expiration", pendingEntry.Duration) 599 registrationEntry.Expiration.Seconds += uint64(pendingEntry.Duration) 600 err = m.DB.UpdateMailboxExpiration(®istrationEntry) 601 if nil != err { 602 m.Logf(LogInfo, "Error updating mailbox expiration: %v\n", err) 603 } 604 _, err = m.DB.DeletePendingRegistration(&pendingEntry) 605 if nil != err { 606 m.Logf(LogInfo, "Error deleting pending registration: %v\n", err) 607 } 608 } 609 return 610 } 611 } 612 613 // deleteMessagesResponse deletes messages from a mailbox. 614 // 615 // @Summary Delete messages 616 // @Description Deletes one or more messages starting from the serial given in the If-Match header. 617 // @Description Requires a valid EdDSA signature in the Taler-Mailbox-Delete-Signature header. 618 // @Tags mailbox 619 // @Param mailbox path string true "Crockford base32-encoded EdDSA public key of the mailbox" 620 // @Param count query int false "Number of messages to delete (default: 1)" 621 // @Param If-Match header string true "Serial number of the first message to delete" 622 // @Param Taler-Mailbox-Delete-Signature header string true "EdDSA signature authorising the deletion" 623 // @Success 204 "Messages deleted" 624 // @Failure 400 "Missing or malformed headers/parameters" 625 // @Failure 403 "Signature invalid" 626 // @Failure 404 "Message with given serial not found" 627 // @Failure 500 628 // @Router /{mailbox} [delete] 629 func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) { 630 vars := mux.Vars(r) 631 etagHeader := r.Header.Get("If-Match") 632 if etagHeader == "" { 633 http.Error(w, "If-Match header missing", 400) 634 return 635 } 636 if strings.Contains(etagHeader, ",") { 637 http.Error(w, "If-Match contains multiple values", 400) 638 return 639 } 640 expectedETag, err := strconv.Atoi(etagHeader) 641 if err != nil { 642 http.Error(w, "If-Match contains malformed etag number", 400) 643 return 644 } 645 pkey, err := util.Base32CrockfordDecode(vars["mailbox"], 32) 646 if err != nil { 647 w.WriteHeader(http.StatusBadRequest) 648 return 649 } 650 count := 1 651 countStr := r.URL.Query().Get("count") 652 if len(countStr) > 0 { 653 count, err = strconv.Atoi(countStr) 654 if err != nil { 655 http.Error(w, "Malformed count parameter", http.StatusBadRequest) 656 w.WriteHeader(http.StatusBadRequest) 657 return 658 } 659 } 660 headerSig := r.Header["Taler-Mailbox-Delete-Signature"] 661 if nil == headerSig { 662 http.Error(w, "Missing signature", http.StatusBadRequest) 663 return 664 } 665 pk := ed25519.PublicKey(pkey) 666 sig, err := util.Base32CrockfordDecode(headerSig[0], 64) 667 if nil != err { 668 w.WriteHeader(http.StatusBadRequest) 669 return 670 } 671 h := sha512.New() 672 h.Write(pkey) 673 hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) 674 m.checkPendingMailboxRegistrationUpdates(hMailbox) 675 var signedMsg [4 * 4]byte 676 binary.BigEndian.PutUint32(signedMsg[0:4], 4*4) 677 binary.BigEndian.PutUint32(signedMsg[4:8], gana.TalerSignaturePurposeMailboxMessagesDelete) 678 binary.BigEndian.PutUint32(signedMsg[8:12], uint32(expectedETag)) 679 binary.BigEndian.PutUint32(signedMsg[12:16], uint32(count)) 680 if !ed25519.Verify(pk, signedMsg[0:], sig) { 681 w.WriteHeader(http.StatusForbidden) 682 return 683 } 684 // Check that expectedETag actually exists 685 var entry InboxEntry 686 err = m.DB.GetInboxEntryBySerial(&entry, hMailbox, int64(expectedETag)) 687 if err != nil { 688 m.Logf(LogDebug, "Message to delete not found with ID %d", expectedETag) 689 w.WriteHeader(http.StatusNotFound) 690 return 691 } 692 m.Logf(LogError, "Deleting from entry %v up to %d messages\n", entry, count) 693 num, err := m.DB.DeleteInboxEntryBySerial(&entry, count) 694 if err != nil { 695 m.Logf(LogDebug, "Failed to delete messages: %v", err) 696 w.WriteHeader(http.StatusInternalServerError) 697 return 698 } 699 m.Logf(LogDebug, "Found matching ID (serial: %d), deleted %d messages", entry.Serial, num) 700 w.WriteHeader(http.StatusNoContent) 701 } 702 703 func (m *Mailbox) termsResponse(w http.ResponseWriter, r *http.Request) { 704 termspath := m.Cfg.Ini.GetFilename("mailbox", "default_terms_path", "terms/", m.Cfg.Datahome) 705 tos.ServiceTermsResponse(w, r, termspath, tos.TalerTosConfig{ 706 DefaultFileType: m.Cfg.Ini.GetString("mailbox", "default_doc_filetype", "text/html"), 707 DefaultLanguage: m.Cfg.Ini.GetString("mailbox", "default_doc_lang", "en"), 708 SupportedFileTypes: strings.Split(m.Cfg.Ini.GetString("mailbox", "supported_doc_filetypes", ""), " "), 709 }) 710 } 711 712 func (m *Mailbox) privacyResponse(w http.ResponseWriter, r *http.Request) { 713 pppath := m.Cfg.Ini.GetFilename("mailbox", "default_pp_path", "privacy/", m.Cfg.Datahome) 714 tos.PrivacyPolicyResponse(w, r, pppath, tos.TalerTosConfig{ 715 DefaultFileType: m.Cfg.Ini.GetString("mailbox", "default_doc_filetype", "text/html"), 716 DefaultLanguage: m.Cfg.Ini.GetString("mailbox", "default_doc_lang", "en"), 717 SupportedFileTypes: strings.Split(m.Cfg.Ini.GetString("mailbox", "supported_doc_filetypes", ""), " "), 718 }) 719 } 720 721 func (m *Mailbox) setupHandlers() { 722 m.Router = mux.NewRouter().StrictSlash(true) 723 724 /* ToS API */ 725 m.Router.HandleFunc("/terms", m.termsResponse).Methods("GET") 726 m.Router.HandleFunc("/privacy", m.privacyResponse).Methods("GET") 727 728 /* Config API */ 729 m.Router.HandleFunc("/config", m.configResponse).Methods("GET") 730 731 /* Mailbox API */ 732 m.Router.HandleFunc("/register", m.registerMailboxResponse).Methods("POST") 733 m.Router.HandleFunc("/info/{h_mailbox}", m.getKeysResponse).Methods("GET") 734 m.Router.HandleFunc("/{h_mailbox}", m.sendMessageResponse).Methods("POST") 735 m.Router.HandleFunc("/{h_mailbox}", m.getMessagesResponse).Methods("GET") 736 m.Router.HandleFunc("/{mailbox}", m.deleteMessagesResponse).Methods("DELETE") 737 } 738 739 func (m *Mailbox) Logf(loglevel LogLevel, fmt string, args ...any) { 740 if loglevel < m.Cfg.Loglevel { 741 return 742 } 743 m.Logger.SetPrefix("taler-mailbox - " + LoglevelStringMap[loglevel] + " ") 744 m.Logger.Printf(fmt, args...) 745 } 746 747 // Initialize the Mailbox instance with cfgfile 748 func (m *Mailbox) Initialize(cfg MailboxConfig) { 749 m.Cfg = cfg 750 m.Logger = log.New(os.Stdout, "taler-mailbox:", log.LstdFlags) 751 m.BaseURL = cfg.Ini.GetString("mailbox", "base_url", "https://example.com") 752 m.MessageBodyBytes = cfg.Ini.GetInt64("mailbox", "message_body_bytes", 256) 753 m.MessageResponseLimit = uint64(cfg.Ini.GetInt64("mailbox", "message_response_limit", 50)) 754 monthlyFee, err := cfg.Ini.GetAmount("mailbox", "monthly_fee", &talerutil.Amount{}) 755 if err != nil { 756 fmt.Printf("Failed to parse monthly fee: %v", err) 757 os.Exit(1) 758 } 759 m.MonthlyFee = monthlyFee 760 updateFee, err := cfg.Ini.GetAmount("mailbox", "registration_update_fee", &talerutil.Amount{}) 761 if err != nil { 762 fmt.Printf("Failed to parse update fee: %v", err) 763 os.Exit(1) 764 } 765 m.RegistrationUpdateFee = updateFee 766 messageFee, err := cfg.Ini.GetAmount("mailbox", "message_fee", &talerutil.Amount{}) 767 if err != nil { 768 fmt.Printf("Failed to parse message fee: %v", err) 769 os.Exit(1) 770 } 771 m.MessageFee = messageFee 772 m.FreeMessageQuota = uint64(cfg.Ini.GetInt64("mailbox", "free_message_quota", 0)) 773 m.DB = cfg.DB 774 go func() { 775 for { 776 num, err := m.DB.DeleteStaleRegistrations(time.Now()) 777 if err != nil { 778 m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) 779 } 780 m.Logf(LogInfo, "Cleaned up %d stale registrations.\n", num) 781 time.Sleep(time.Hour * 24) 782 } 783 }() 784 // Clean up pending 785 pendingExp, err := cfg.Ini.GetDuration("mailbox", "pending_registration_expiration", 24*time.Hour) 786 if err != nil { 787 fmt.Printf("Failed to parse pending registration expiration: %v", err) 788 os.Exit(1) 789 } 790 go func() { 791 for { 792 num, err := m.DB.DeleteStalePendingRegistrations(time.Now().Add(-pendingExp)) 793 if err != nil { 794 m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) 795 } 796 m.Logf(LogInfo, "Cleaned up %d stale pending registrations.\n", num) 797 time.Sleep(pendingExp) 798 } 799 }() 800 801 m.Merchant = cfg.Merchant 802 if !monthlyFee.IsZero() { 803 merchConfig, err := m.Merchant.GetConfig() 804 if err != nil { 805 fmt.Printf("Failed to get merchant config: %v", err) 806 os.Exit(1) 807 } 808 currencySpec, currencySupported := merchConfig.Currencies[monthlyFee.Currency] 809 for !currencySupported { 810 fmt.Printf("Currency `%s' not supported by merchant!\n", monthlyFee.Currency) 811 os.Exit(1) 812 } 813 m.CurrencySpec = currencySpec 814 } 815 m.setupHandlers() 816 }