mailbox.go (29550B)
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 // ErrorDetail is the detailed error payload returned from Mailbox endpoints 61 type ErrorDetail struct { 62 63 // Numeric error code unique to the condition. 64 // The other arguments are specific to the error value reported here. 65 Code int `json:"code"` 66 67 // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ... 68 // Should give a human-readable hint about the error's nature. Optional, may change without notice! 69 Hint string `json:"hint,omitempty"` 70 71 // Optional detail about the specific input value that failed. May change without notice! 72 Detail string `json:"detail,omitempty"` 73 } 74 75 76 type MailboxConfig struct { 77 // libtool-style representation of the Mailbox protocol version, see 78 // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning 79 // The format is "current:revision:age". 80 LibtoolVersion string 81 82 // Version 83 Version string 84 85 // Data home 86 Datahome string 87 88 // Configuration 89 Ini talerutil.TalerConfiguration 90 91 // The database connection to use 92 DB *MailboxDatabase 93 94 // Merchant connection 95 Merchant merchant.Merchant 96 97 // Loglevel 98 Loglevel LogLevel 99 } 100 101 // Mailbox is the primary object of the Mailbox service 102 type Mailbox struct { 103 104 // The main router 105 Router *mux.Router 106 107 // The database connection to use 108 DB *MailboxDatabase 109 110 // Our configuration from the ini 111 Cfg MailboxConfig 112 113 // Fixed size of message bodies 114 MessageBodyBytes int64 `json:"message_body_bytes"` 115 116 // Merchant object 117 Merchant merchant.Merchant 118 119 // Base URL 120 BaseURL string 121 122 // Registration fee for each validity month mailbox 123 MonthlyFee *talerutil.Amount 124 125 // Registration fee for registering or modifying mailbox 126 RegistrationUpdateFee *talerutil.Amount 127 128 // Message fee for receiving a message 129 MessageFee *talerutil.Amount 130 131 // The free message quota 132 FreeMessageQuota uint64 133 134 // How many messages will a single response 135 // contain at maximum. 136 MessageResponseLimit uint64 137 138 // Logger 139 Logger *log.Logger 140 141 // Currency Spec 142 CurrencySpec talerutil.CurrencySpecification 143 } 144 145 type RelativeTime struct { 146 Microseconds uint64 `json:"d_us"` 147 } 148 149 // 1 Month as Go duration 150 const monthDuration = time.Hour * 24 * 30 151 152 // VersionResponse is the JSON response of the /config endpoint 153 type VersionResponse struct { 154 // libtool-style representation of the Mailbox protocol version, see 155 // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning 156 // The format is "current:revision:age". 157 Version string `json:"version"` 158 159 // Name of the protocol. 160 Name string `json:"name"` // "taler-mailbox" 161 162 // Fixed size of message bodies 163 MessageBodyBytes int64 `json:"message_body_bytes"` 164 165 // How long will the service store a message 166 // before giving up 167 DeliveryPeriod RelativeTime `json:"delivery_period" gorm:"embedded;embeddedPrefix:delivery_period_"` 168 169 // How many messages will a single response 170 // contain at maximum. 171 MessageResponseLimit uint64 `json:"message_response_limit"` 172 173 // How much is the cost of a single 174 // registration (update) of a mailbox 175 // May be 0 for a free update/registration. 176 RegistrationUpdateFee string `json:"registration_update_fee"` 177 178 // How much is the cost of a single 179 // registration period (30 days) of a mailbox 180 // May be 0 for a free registration. 181 MonthlyFee string `json:"monthly_fee"` 182 183 // How much is the cost to send a single 184 // message to a mailbox. 185 // May be 0 for free message sending. 186 MessageFee string `json:"message_fee"` 187 188 // How many messages can be send and 189 // are stored by the service for free. 190 // After the quota is reached, the 191 // regular message_fee applies. 192 // May be 0 for no free quota. 193 FreeMessageQuota uint64 `json:"free_message_quota"` 194 } 195 196 type MailboxRegistrationRequest struct { 197 198 // Keys to add/update for a mailbox. 199 MailboxMetadata MailboxMetadata `json:"mailbox_metadata"` 200 201 // Signature by the mailbox's signing key affirming 202 // the update of keys, of purpose 203 // TALER_SIGNATURE_WALLET_MAILBOX_KEYS_UPDATE. 204 // The signature is created over the SHA-512 hash 205 // of (encryptionKeyType||encryptionKey||expiration) 206 Signature string `json:"signature"` 207 } 208 209 // MailboxRateLimitedResponse is the JSON response when a rate limit is hit 210 type MailboxRateLimitedResponse struct { 211 212 // Taler error code, TALER_EC_mailbox_REGISTER_RATE_LIMITED. 213 Code int `json:"code"` 214 215 // When the client should retry. Currently: In microseconds 216 RetryDelay int64 `json:"retry_delay"` 217 218 // The human readable error message. 219 Hint string `json:"hint"` 220 } 221 222 // configResponse returns the service configuration. 223 // 224 // @Summary Get service configuration 225 // @Description Returns service metadata including fees, message size limits, and delivery period. 226 // @Tags config 227 // @Produce json 228 // @Success 200 {object} VersionResponse 229 // @Router /config [get] 230 func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { 231 dp, err := m.Cfg.Ini.GetDuration("mailbox", "delivery_period", 3*24*time.Hour) 232 if err != nil { 233 log.Fatal(err) 234 } 235 cfg := VersionResponse{ 236 Version: m.Cfg.LibtoolVersion, 237 Name: "taler-mailbox", 238 MessageBodyBytes: m.MessageBodyBytes, 239 MessageResponseLimit: m.MessageResponseLimit, 240 MonthlyFee: m.MonthlyFee.String(), 241 RegistrationUpdateFee: m.RegistrationUpdateFee.String(), 242 DeliveryPeriod: RelativeTime{Microseconds: uint64(dp.Microseconds())}, 243 MessageFee: m.MessageFee.String(), 244 FreeMessageQuota: m.FreeMessageQuota, 245 } 246 w.Header().Set("Content-Type", "application/json") 247 response, _ := json.Marshal(cfg) 248 w.Write(response) 249 } 250 251 // getMessagesResponse retrieves pending messages for a mailbox. 252 // 253 // @Summary Retrieve messages 254 // @Description Returns up to MessageResponseLimit encrypted message bodies for the given mailbox. 255 // @Description The ETag response header contains the serial number of the first message. 256 // @Tags mailbox 257 // @Produce application/octet-stream 258 // @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)" 259 // @Success 200 "One or more message bodies concatenated" 260 // @Success 204 "No messages available" 261 // @Failure 404 "Mailbox not found" 262 // @Router /{h_mailbox} [get] 263 func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { 264 vars := mux.Vars(r) 265 //to, toSet := vars["timeout_ms"] 266 var entries []InboxEntry 267 // FIXME rate limit 268 // FIXME timeout 269 // FIXME possibly limit results here 270 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 271 entries, err := m.DB.GetMessages(vars["h_mailbox"], int(m.MessageResponseLimit)) 272 if err != nil { 273 m.Logf(LogError, "Error getting messages: %v", err) 274 w.WriteHeader(http.StatusNotFound) 275 return 276 } 277 if len(entries) == 0 { 278 w.WriteHeader(http.StatusNoContent) 279 return 280 } 281 // Add ETag of first message ID 282 etag := entries[0].Serial 283 w.Header().Add("ETag", fmt.Sprintf("%d", etag)) 284 for _, entry := range entries { 285 w.Write(entry.Body) 286 } 287 } 288 289 // sendMessageResponse delivers a message to a mailbox. 290 // 291 // @Summary Send a message 292 // @Description Stores an encrypted message body for the given mailbox. The body must be 293 // @Description exactly MessageBodyBytes in size. 294 // @Tags mailbox 295 // @Accept application/octet-stream 296 // @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)" 297 // @Param body body string true "Encrypted message body (fixed size)" 298 // @Success 204 "Message stored" 299 // @Success 304 "Identical message already stored" 300 // @Failure 400 "Missing or wrong-size body" 301 // @Failure 402 "Payment required (free quota exceeded)" 302 // @Failure 500 303 // @Router /{h_mailbox} [post] 304 func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { 305 vars := mux.Vars(r) 306 var entry InboxEntry 307 var errDetail ErrorDetail 308 if r.Body == nil { 309 errDetail.Code = gana.WALLET_MAILBOX_MESSAGE_SEND_DENIED 310 errDetail.Hint = "No request body." 311 resp, _ := json.Marshal(errDetail) 312 w.WriteHeader(http.StatusForbidden) 313 w.Write(resp) 314 return 315 } 316 if r.ContentLength != m.MessageBodyBytes { 317 errDetail.Code = gana.WALLET_MAILBOX_MESSAGE_SEND_DENIED 318 errDetail.Hint = "Wrong message size." 319 resp, _ := json.Marshal(errDetail) 320 w.WriteHeader(http.StatusForbidden) 321 w.Write(resp) 322 return 323 } 324 body, err := io.ReadAll(r.Body) 325 if err != nil { 326 log.Printf("%v", err) 327 errDetail.Code = gana.WALLET_MAILBOX_MESSAGE_SEND_DENIED 328 errDetail.Hint = "Cannot read body." 329 resp, _ := json.Marshal(errDetail) 330 w.WriteHeader(http.StatusForbidden) 331 w.Write(resp) 332 return 333 } 334 if !m.MessageFee.IsZero() { 335 var count int64 336 count, err = m.DB.GetMessagesCount(vars["h_mailbox"]) 337 if nil != err { 338 m.Logf(LogError, "Error getting messages: %v", err) 339 http.Error(w, "Cannot look for entries", http.StatusBadRequest) 340 return 341 } 342 if count >= int64(m.FreeMessageQuota) { 343 w.WriteHeader(http.StatusPaymentRequired) 344 //w.Header().Set("Taler", payto) FIXME generate payto 345 return 346 } 347 } 348 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 349 err = m.DB.GetInboxEntryBySigningKeyAndBody(&entry, vars["h_mailbox"], body) 350 if err == nil { 351 w.WriteHeader(http.StatusNotModified) 352 return 353 } 354 entry.HashedSigningKey = vars["h_mailbox"] 355 entry.Body = body 356 err = m.DB.InsertInboxEntry(&entry) 357 if err != nil { 358 m.Logf(LogError, "Error storing message: %v", err) 359 w.WriteHeader(http.StatusInternalServerError) 360 return 361 } 362 w.WriteHeader(http.StatusNoContent) 363 } 364 365 // getKeysResponse returns the public key metadata for a registered mailbox. 366 // 367 // @Summary Get mailbox info 368 // @Description Returns the signing and encryption key metadata for the given mailbox. 369 // @Tags mailbox 370 // @Produce json 371 // @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)" 372 // @Success 200 {object} MailboxMetadata 373 // @Failure 404 "Mailbox not found or expired" 374 // @Router /info/{h_mailbox} [get] 375 func (m *Mailbox) getKeysResponse(w http.ResponseWriter, r *http.Request) { 376 vars := mux.Vars(r) 377 var keyEntry MailboxMetadata 378 m.checkPendingMailboxRegistrationUpdates(vars["h_mailbox"]) 379 err := m.DB.GetMailboxMetadataBySigningKey(&keyEntry, vars["h_mailbox"]) 380 if err != nil { 381 m.Logf(LogError, "Error finding mailbox: %v", err) 382 w.WriteHeader(http.StatusNotFound) 383 return 384 } 385 m.Logf(LogDebug, "entry expires at %d, have %d", keyEntry.Expiration, time.Now().Unix()) 386 if int64(keyEntry.Expiration.Seconds) < int64(time.Now().Unix()) { 387 w.WriteHeader(http.StatusNotFound) 388 return 389 } 390 response, _ := json.Marshal(keyEntry) 391 w.Write(response) 392 } 393 394 func (m *Mailbox) validateRegistrationSignature(msg MailboxRegistrationRequest) error { 395 var expNbo [8]byte 396 var signedMsg [72]byte 397 encPk, err := util.Base32CrockfordDecode(msg.MailboxMetadata.EncryptionKey, 32) 398 if err != nil { 399 return fmt.Errorf("unable to decode encryption key") 400 } 401 pkey, err := util.Base32CrockfordDecode(msg.MailboxMetadata.SigningKey, 32) 402 if err != nil { 403 return fmt.Errorf("unable to decode pubkey") 404 } 405 pk := ed25519.PublicKey(pkey) 406 sig, err := util.Base32CrockfordDecode(msg.Signature, 64) 407 if nil != err { 408 return fmt.Errorf("unable to decode signature") 409 } 410 binary.BigEndian.PutUint64(expNbo[:], msg.MailboxMetadata.Expiration.Seconds) 411 size := signedMsg[0:4] 412 binary.BigEndian.PutUint32(size, 64+4+4) 413 purp := signedMsg[4:8] 414 binary.BigEndian.PutUint32(purp, gana.TalerSignaturePurposeMailboxRegister) 415 h := sha512.New() 416 h.Write([]byte(msg.MailboxMetadata.EncryptionKeyType)) // Currently always X25519 417 h.Write(encPk) 418 h.Write(expNbo[:]) 419 copy(signedMsg[8:], h.Sum(nil)) 420 if !ed25519.Verify(pk, signedMsg[0:], sig) { 421 return fmt.Errorf("signature invalid") 422 } 423 return nil 424 } 425 426 // Check if this is a non-zero, positive amount 427 func calculateCost(sliceCostAmount string, fixedCostAmount string, howLong time.Duration, sliceDuration time.Duration) (*talerutil.Amount, error) { 428 sliceCount := int(float64(howLong.Microseconds()) / float64(sliceDuration.Microseconds())) 429 sliceCost, err := talerutil.ParseAmount(sliceCostAmount) 430 if nil != err { 431 return nil, err 432 } 433 fixedCost, err := talerutil.ParseAmount(fixedCostAmount) 434 if nil != err { 435 return nil, err 436 } 437 sum := &talerutil.Amount{ 438 Currency: sliceCost.Currency, 439 Value: 0, 440 Fraction: 0, 441 } 442 for range sliceCount { 443 sum, err = sum.Add(*sliceCost) 444 if nil != err { 445 return nil, err 446 } 447 } 448 sum, err = sum.Add(*fixedCost) 449 if nil != err { 450 return nil, err 451 } 452 return sum, nil 453 } 454 455 // registerMailboxResponse registers or updates a mailbox. 456 // 457 // @Summary Register or update mailbox 458 // @Description Registers a new mailbox or updates the keys/expiration of an existing one. 459 // @Description A valid EdDSA signature over the key material must be provided. 460 // @Tags mailbox 461 // @Accept json 462 // @Param body body MailboxRegistrationRequest true "Registration request" 463 // @Success 204 "Registration confirmed" 464 // @Success 304 "Nothing changed" 465 // @Failure 400 "Invalid request body or signature" 466 // @Failure 402 "Payment required" 467 // @Failure 500 468 // @Router /register [post] 469 func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request) { 470 var msg MailboxRegistrationRequest 471 var pendingRegistration PendingMailboxRegistration 472 var registrationEntry MailboxMetadata 473 if r.Body == nil { 474 m.Logf(LogError, "no request body") 475 http.Error(w, "No request body", http.StatusBadRequest) 476 return 477 } 478 err := json.NewDecoder(r.Body).Decode(&msg) 479 if err != nil { 480 http.Error(w, "Malformed request body", http.StatusBadRequest) 481 return 482 } 483 pkey, err := util.Base32CrockfordDecode(msg.MailboxMetadata.SigningKey, 32) 484 if err != nil { 485 http.Error(w, "Public key invalid", http.StatusBadRequest) 486 return 487 } 488 pk := ed25519.PublicKey(pkey) 489 err = m.validateRegistrationSignature(msg) 490 var errDetail ErrorDetail 491 if nil != err { 492 errDetail.Code = gana.WALLET_MAILBOX_REGISTER_INVALID_SIGNATURE 493 errDetail.Hint = "Signature invalid." 494 resp, _ := json.Marshal(errDetail) 495 w.WriteHeader(http.StatusForbidden) 496 w.Write(resp) 497 return 498 } 499 h := sha512.New() 500 h.Write(pkey) 501 hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) 502 pendingRegistration.HashedSigningKey = hMailbox 503 // Round to the nearest multiple of a month 504 reqExpiration := time.Unix(int64(msg.MailboxMetadata.Expiration.Seconds), 0) 505 now := time.Now() 506 reqDuration := reqExpiration.Sub(now).Round(monthDuration) 507 err = m.DB.GetMailboxMetadataBySigningKey(®istrationEntry, hMailbox) 508 if err == nil { 509 // This probably means the registration is modified or extended or both 510 entryModified := (registrationEntry.EncryptionKey != msg.MailboxMetadata.EncryptionKey) 511 // At least one MonthlyFee 512 if reqDuration.Microseconds() == 0 && !entryModified { 513 // Nothing changed. Return validity 514 w.WriteHeader(http.StatusNotModified) 515 return 516 } 517 } else { 518 // Entry does not yet exist, add but immediately expire it 519 registrationEntry = msg.MailboxMetadata 520 registrationEntry.Expiration.Seconds = uint64(time.Now().Unix() - 1) 521 hAddr := sha512.New() 522 hAddr.Write(pk) 523 registrationEntry.HashedSigningKey = util.Base32CrockfordEncode(hAddr.Sum(nil)) 524 err = m.DB.InsertMailboxRegistration(®istrationEntry) 525 if nil != err { 526 m.Logf(LogError, "%v\n", err) 527 w.WriteHeader(http.StatusInternalServerError) 528 return 529 } 530 } 531 err = m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingRegistration, hMailbox) 532 pendingRegistrationExists := (nil == err) 533 if !pendingRegistrationExists { 534 pendingRegistration.HashedSigningKey = hMailbox 535 pendingRegistration.Duration = reqDuration.Microseconds() 536 err = m.DB.InsertPendingMailboxRegistration(&pendingRegistration) 537 if nil != err { 538 m.Logf(LogError, "Error inserting pending registration: %v\n", err) 539 w.WriteHeader(http.StatusInternalServerError) 540 return 541 } 542 err = m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingRegistration, hMailbox) 543 if nil != err { 544 m.Logf(LogError, "Error getting pending registration: %v\n", err) 545 w.WriteHeader(http.StatusInternalServerError) 546 return 547 } 548 } 549 // At least the update fee needs to be paid 550 cost, err := calculateCost(m.MonthlyFee.String(), 551 m.RegistrationUpdateFee.String(), 552 reqDuration, 553 monthDuration) 554 if err != nil { 555 m.Logf(LogError, "Error calculating cost: %v\n", err) 556 w.WriteHeader(http.StatusInternalServerError) 557 return 558 } 559 if !cost.IsZero() { 560 if len(pendingRegistration.OrderID) == 0 { 561 // Add new order 562 orderID, newOrderErr := m.Merchant.AddNewOrder(*cost, "Mailbox registration", m.BaseURL) 563 if newOrderErr != nil { 564 m.Logf(LogError, "Error adding order: %v", newOrderErr) 565 w.WriteHeader(http.StatusInternalServerError) 566 return 567 } 568 m.Logf(LogDebug, "New order ID %s for pending registration for %s", orderID, pendingRegistration.HashedSigningKey) 569 pendingRegistration.OrderID = orderID 570 } 571 // Check if order paid. 572 // FIXME: Remember that it was activated and paid 573 // FIXME: We probably need to handle the return code here (see gns registrar for how) 574 _, _, payto, paytoErr := m.Merchant.IsOrderPaid(pendingRegistration.OrderID) 575 if paytoErr != nil { 576 fmt.Println(paytoErr) 577 w.WriteHeader(http.StatusInternalServerError) 578 m.Logf(LogError, "Error checking if order is paid: %s\n", paytoErr.Error()) 579 return 580 } 581 if len(payto) != 0 { 582 err = m.DB.UpdatePendingMailboxRegistrationOrderId(&pendingRegistration) 583 if err != nil { 584 m.Logf(LogError, "Error updating pending registration: %v\n", err) 585 w.WriteHeader(http.StatusInternalServerError) 586 return 587 } 588 w.WriteHeader(http.StatusPaymentRequired) 589 w.Header().Set("Taler", payto) 590 return 591 } 592 } 593 // Update expiration time of registration. 594 registrationEntry.Expiration.Seconds += uint64(reqDuration.Seconds()) 595 _, err = m.DB.DeletePendingRegistration(&pendingRegistration) 596 if nil != err { 597 m.Logf(LogError, "Error deleting pending registration: %v\n", err) 598 w.WriteHeader(http.StatusInternalServerError) 599 return 600 } 601 err = m.DB.UpdateMailboxExpiration(®istrationEntry) 602 if nil != err { 603 m.Logf(LogError, "Error updating mailbox registration: %v\n", err) 604 w.WriteHeader(http.StatusInternalServerError) 605 return 606 } 607 w.WriteHeader(http.StatusNoContent) 608 } 609 610 func (m *Mailbox) checkPendingMailboxRegistrationUpdates(hMailbox string) { 611 var pendingEntry PendingMailboxRegistration 612 var registrationEntry MailboxMetadata 613 err := m.DB.GetPendingMailboxRegistrationBySigningKey(&pendingEntry, hMailbox) 614 if err != nil { 615 return 616 } 617 m.Logf(LogDebug, "Found pending registration for %s, OrderID: %s", hMailbox, pendingEntry.OrderID) 618 rc, orderStatus, _, paytoErr := m.Merchant.IsOrderPaid(pendingEntry.OrderID) 619 if nil != paytoErr { 620 if rc == http.StatusNotFound { 621 m.Logf(LogInfo, "Registration order for `%s' not found, removing\n", hMailbox) 622 } 623 _, err = m.DB.DeletePendingRegistration(&pendingEntry) 624 if nil != err { 625 m.Logf(LogInfo, "Error deleting pending registration: %v\n", err) 626 } 627 return 628 } 629 m.Logf(LogDebug, "Order status for %s is %s", pendingEntry.HashedSigningKey, orderStatus) 630 if merchant.OrderPaid == orderStatus { 631 m.Logf(LogDebug, "Order for %v appears to be paid", pendingEntry) 632 err = m.DB.GetMailboxMetadataBySigningKey(®istrationEntry, hMailbox) 633 if err == nil { 634 m.Logf(LogDebug, "Adding %d seconds to entry expiration", pendingEntry.Duration) 635 registrationEntry.Expiration.Seconds += uint64(pendingEntry.Duration) 636 err = m.DB.UpdateMailboxExpiration(®istrationEntry) 637 if nil != err { 638 m.Logf(LogInfo, "Error updating mailbox expiration: %v\n", err) 639 } 640 _, err = m.DB.DeletePendingRegistration(&pendingEntry) 641 if nil != err { 642 m.Logf(LogInfo, "Error deleting pending registration: %v\n", err) 643 } 644 } 645 return 646 } 647 } 648 649 // deleteMessagesResponse deletes messages from a mailbox. 650 // 651 // @Summary Delete messages 652 // @Description Deletes one or more messages starting from the serial given in the If-Match header. 653 // @Description Requires a valid EdDSA signature in the Taler-Mailbox-Delete-Signature header. 654 // @Tags mailbox 655 // @Param mailbox path string true "Crockford base32-encoded EdDSA public key of the mailbox" 656 // @Param count query int false "Number of messages to delete (default: 1)" 657 // @Param If-Match header string true "Serial number of the first message to delete" 658 // @Param Taler-Mailbox-Delete-Signature header string true "EdDSA signature authorising the deletion" 659 // @Success 204 "Messages deleted" 660 // @Failure 400 "Missing or malformed headers/parameters" 661 // @Failure 403 "Signature invalid" 662 // @Failure 404 "Message with given serial not found" 663 // @Failure 500 664 // @Router /{mailbox} [delete] 665 func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) { 666 var errDetail ErrorDetail 667 vars := mux.Vars(r) 668 etagHeader := r.Header.Get("If-Match") 669 if etagHeader == "" { 670 http.Error(w, "If-Match header missing", http.StatusBadRequest) 671 return 672 } 673 if strings.Contains(etagHeader, ",") { 674 http.Error(w, "If-Match contains multiple values", http.StatusBadRequest) 675 return 676 } 677 expectedETag, err := strconv.Atoi(etagHeader) 678 if err != nil { 679 http.Error(w, "If-Match contains malformed etag number", http.StatusBadRequest) 680 return 681 } 682 pkey, err := util.Base32CrockfordDecode(vars["mailbox"], 32) 683 if err != nil { 684 http.Error(w, "Parameter encoding invalid.", http.StatusBadRequest) 685 return 686 } 687 count := 1 688 countStr := r.URL.Query().Get("count") 689 if len(countStr) > 0 { 690 count, err = strconv.Atoi(countStr) 691 if err != nil { 692 http.Error(w, "Malformed count parameter", http.StatusBadRequest) 693 w.WriteHeader(http.StatusBadRequest) 694 return 695 } 696 } 697 headerSig := r.Header["Taler-Mailbox-Delete-Signature"] 698 if nil == headerSig { 699 http.Error(w, "Missing signature", http.StatusBadRequest) 700 return 701 } 702 pk := ed25519.PublicKey(pkey) 703 sig, err := util.Base32CrockfordDecode(headerSig[0], 64) 704 if nil != err { 705 w.WriteHeader(http.StatusBadRequest) 706 return 707 } 708 h := sha512.New() 709 h.Write(pkey) 710 hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) 711 m.checkPendingMailboxRegistrationUpdates(hMailbox) 712 var signedMsg [4 * 4]byte 713 binary.BigEndian.PutUint32(signedMsg[0:4], 4*4) 714 binary.BigEndian.PutUint32(signedMsg[4:8], gana.TalerSignaturePurposeMailboxMessagesDelete) 715 binary.BigEndian.PutUint32(signedMsg[8:12], uint32(expectedETag)) 716 binary.BigEndian.PutUint32(signedMsg[12:16], uint32(count)) 717 if !ed25519.Verify(pk, signedMsg[0:], sig) { 718 errDetail.Code = gana.WALLET_MAILBOX_MESSAGE_DELETE_DENIED 719 errDetail.Hint = "Signature invalid." 720 resp, _ := json.Marshal(errDetail) 721 w.WriteHeader(http.StatusForbidden) 722 w.Write(resp) 723 return 724 } 725 // Check that expectedETag actually exists 726 var entry InboxEntry 727 err = m.DB.GetInboxEntryBySerial(&entry, hMailbox, int64(expectedETag)) 728 if err != nil { 729 m.Logf(LogDebug, "Message to delete not found with ID %d", expectedETag) 730 errDetail.Code = gana.WALLET_MAILBOX_MESSAGE_DELETE_FAILED 731 errDetail.Hint = "Message to delete not found." 732 resp, _ := json.Marshal(errDetail) 733 w.Write(resp) 734 w.WriteHeader(http.StatusNotFound) 735 return 736 } 737 m.Logf(LogError, "Deleting from entry %v up to %d messages\n", entry, count) 738 num, err := m.DB.DeleteInboxEntryBySerial(&entry, count) 739 if err != nil { 740 m.Logf(LogDebug, "Failed to delete messages: %v", err) 741 w.WriteHeader(http.StatusInternalServerError) 742 return 743 } 744 m.Logf(LogDebug, "Found matching ID (serial: %d), deleted %d messages", entry.Serial, num) 745 w.WriteHeader(http.StatusNoContent) 746 } 747 748 func (m *Mailbox) termsResponse(w http.ResponseWriter, r *http.Request) { 749 termspath := m.Cfg.Ini.GetFilename("mailbox", "default_terms_path", "terms/", m.Cfg.Datahome) 750 tos.ServiceTermsResponse(w, r, termspath, tos.TalerTosConfig{ 751 DefaultFileType: m.Cfg.Ini.GetString("mailbox", "default_doc_filetype", "text/html"), 752 DefaultLanguage: m.Cfg.Ini.GetString("mailbox", "default_doc_lang", "en"), 753 SupportedFileTypes: strings.Split(m.Cfg.Ini.GetString("mailbox", "supported_doc_filetypes", ""), " "), 754 }) 755 } 756 757 func (m *Mailbox) privacyResponse(w http.ResponseWriter, r *http.Request) { 758 pppath := m.Cfg.Ini.GetFilename("mailbox", "default_pp_path", "privacy/", m.Cfg.Datahome) 759 tos.PrivacyPolicyResponse(w, r, pppath, tos.TalerTosConfig{ 760 DefaultFileType: m.Cfg.Ini.GetString("mailbox", "default_doc_filetype", "text/html"), 761 DefaultLanguage: m.Cfg.Ini.GetString("mailbox", "default_doc_lang", "en"), 762 SupportedFileTypes: strings.Split(m.Cfg.Ini.GetString("mailbox", "supported_doc_filetypes", ""), " "), 763 }) 764 } 765 766 func (m *Mailbox) setupHandlers() { 767 m.Router = mux.NewRouter().StrictSlash(true) 768 769 /* ToS API */ 770 m.Router.HandleFunc("/terms", m.termsResponse).Methods("GET") 771 m.Router.HandleFunc("/privacy", m.privacyResponse).Methods("GET") 772 773 /* Config API */ 774 m.Router.HandleFunc("/config", m.configResponse).Methods("GET") 775 776 /* Mailbox API */ 777 m.Router.HandleFunc("/register", m.registerMailboxResponse).Methods("POST") 778 m.Router.HandleFunc("/info/{h_mailbox}", m.getKeysResponse).Methods("GET") 779 m.Router.HandleFunc("/{h_mailbox}", m.sendMessageResponse).Methods("POST") 780 m.Router.HandleFunc("/{h_mailbox}", m.getMessagesResponse).Methods("GET") 781 m.Router.HandleFunc("/{mailbox}", m.deleteMessagesResponse).Methods("DELETE") 782 } 783 784 func (m *Mailbox) Logf(loglevel LogLevel, fmt string, args ...any) { 785 if loglevel < m.Cfg.Loglevel { 786 return 787 } 788 m.Logger.SetPrefix("taler-mailbox - " + LoglevelStringMap[loglevel] + " ") 789 m.Logger.Printf(fmt, args...) 790 } 791 792 // Initialize the Mailbox instance with cfgfile 793 func (m *Mailbox) Initialize(cfg MailboxConfig) { 794 m.Cfg = cfg 795 m.Logger = log.New(os.Stdout, "taler-mailbox:", log.LstdFlags) 796 m.BaseURL = cfg.Ini.GetString("mailbox", "base_url", "https://example.com") 797 m.MessageBodyBytes = cfg.Ini.GetInt64("mailbox", "message_body_bytes", 256) 798 m.MessageResponseLimit = uint64(cfg.Ini.GetInt64("mailbox", "message_response_limit", 50)) 799 monthlyFee, err := cfg.Ini.GetAmount("mailbox", "monthly_fee", &talerutil.Amount{}) 800 if err != nil { 801 fmt.Printf("Failed to parse monthly fee: %v", err) 802 os.Exit(1) 803 } 804 m.MonthlyFee = monthlyFee 805 updateFee, err := cfg.Ini.GetAmount("mailbox", "registration_update_fee", &talerutil.Amount{}) 806 if err != nil { 807 fmt.Printf("Failed to parse update fee: %v", err) 808 os.Exit(1) 809 } 810 m.RegistrationUpdateFee = updateFee 811 messageFee, err := cfg.Ini.GetAmount("mailbox", "message_fee", &talerutil.Amount{}) 812 if err != nil { 813 fmt.Printf("Failed to parse message fee: %v", err) 814 os.Exit(1) 815 } 816 m.MessageFee = messageFee 817 m.FreeMessageQuota = uint64(cfg.Ini.GetInt64("mailbox", "free_message_quota", 0)) 818 m.DB = cfg.DB 819 go func() { 820 for { 821 num, err := m.DB.DeleteStaleRegistrations(time.Now()) 822 if err != nil { 823 m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) 824 } 825 m.Logf(LogInfo, "Cleaned up %d stale registrations.\n", num) 826 time.Sleep(time.Hour * 24) 827 } 828 }() 829 // Clean up pending 830 pendingExp, err := cfg.Ini.GetDuration("mailbox", "pending_registration_expiration", 24*time.Hour) 831 if err != nil { 832 fmt.Printf("Failed to parse pending registration expiration: %v", err) 833 os.Exit(1) 834 } 835 go func() { 836 for { 837 num, err := m.DB.DeleteStalePendingRegistrations(time.Now().Add(-pendingExp)) 838 if err != nil { 839 m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) 840 } 841 m.Logf(LogInfo, "Cleaned up %d stale pending registrations.\n", num) 842 time.Sleep(pendingExp) 843 } 844 }() 845 846 m.Merchant = cfg.Merchant 847 if !monthlyFee.IsZero() { 848 merchConfig, err := m.Merchant.GetConfig() 849 if err != nil { 850 fmt.Printf("Failed to get merchant config: %v", err) 851 os.Exit(1) 852 } 853 currencySpec, currencySupported := merchConfig.Currencies[monthlyFee.Currency] 854 for !currencySupported { 855 fmt.Printf("Currency `%s' not supported by merchant!\n", monthlyFee.Currency) 856 os.Exit(1) 857 } 858 m.CurrencySpec = currencySpec 859 } 860 m.setupHandlers() 861 }