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