taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

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, &registrationEntry, 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, &registrationEntry)
    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, &registrationEntry)
    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, &registrationEntry, 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, &registrationEntry)
    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 }