gnunet-gns-registrar

GNU Name System registrar
Log | Files | Refs | README

gnsregistrar.go (39919B)


      1 // This file is part of gnsregistrar, a GNS registrar implementation.
      2 // Copyright (C) 2022 Martin Schanzenbach
      3 //
      4 // gnsregistrar 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 // gnsregistrar 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 gnsregistrar
     20 
     21 import (
     22 	"bytes"
     23 	"encoding/base64"
     24 	"encoding/json"
     25 	"errors"
     26 	"fmt"
     27 	"html/template"
     28 	"io"
     29 	"log"
     30 	"net/http"
     31 	"net/url"
     32 	"os"
     33 	"os/exec"
     34 	"regexp"
     35 	"strings"
     36 	"time"
     37 
     38 	"github.com/google/uuid"
     39 	"github.com/gorilla/mux"
     40 	"github.com/schanzen/taler-go/pkg/merchant"
     41 	talerutil "github.com/schanzen/taler-go/pkg/util"
     42 	"github.com/skip2/go-qrcode"
     43 	"gopkg.in/ini.v1"
     44 )
     45 
     46 type RegistrarConfig struct {
     47 	// The config to use
     48 	Ini *ini.File
     49 
     50 	// The Service Version
     51 	Version string
     52 
     53 	// The data home location (usually $datadir/gnunet-gns-registrar)
     54 	Datahome string
     55 
     56 	// The merchant connection to use
     57 	Merchant merchant.Merchant
     58 
     59 	// The loglevel to use
     60 	Loglevel LogLevel
     61 }
     62 
     63 // This is metadata stored in the namestore next to a registered zone key.
     64 // It is always stored in a private metadata record and holds registration information such
     65 // as payment status and expiration.
     66 type RegistrationMetadata struct {
     67 	// The expiration in GNS-compatible 64-bit microseconds epoch.
     68 	Expiration uint64 `json:"expiration"`
     69 
     70 	// Indication if this registration is already paid and active.
     71 	Paid bool `json:"paid"`
     72 
     73 	// The unique order identifier (as received through https://docs.taler.net/core/api-merchant.html).
     74 	OrderID string `json:"order_id"`
     75 
     76 	// The unique registration identifier used as token for registration management by customer.
     77 	RegistrationID string `json:"registration_id"`
     78 
     79 	// The payment deadline. FIXME this may also be part of the order somehow in the Taler API.
     80 	NeedsPaymentUntil time.Time `json:"needs_payment_until"`
     81 }
     82 
     83 // See https://docs.gnunet.org/latest/developers/rest-api/identity.html
     84 type IdentityInfo struct {
     85 	// Base32-encoded GNS zone key
     86 	Pubkey string `json:"pubkey"`
     87 
     88 	// The name of the zone/identity
     89 	Name string `json:"name"`
     90 }
     91 
     92 // See https://gana.gnunet.org/gnunet-error-codes/gnunet_error_codes.html
     93 type GnunetError struct {
     94 	// Error description
     95 	Description string `json:"error"`
     96 
     97 	// Error code
     98 	Code uint32 `json:"error_code"`
     99 }
    100 
    101 // See https://docs.gnunet.org/latest/developers/rest-api/namestore.html
    102 type RecordData struct {
    103 	// The string representation of the record data value, e.g. "1.2.3.4" for an A record
    104 	Value string `json:"value"`
    105 
    106 	// The string representation of the record type, e.g. "A" for an IPv4 address
    107 	RecordType string `json:"record_type"`
    108 
    109 	// The relative expiration time, in microseconds. Set if is_relative_expiration: true
    110 	RelativeExpiration uint64 `json:"relative_expiration"`
    111 
    112 	// Whether or not this is a private record
    113 	IsPrivate bool `json:"is_private"`
    114 
    115 	// Whether or not the expiration time is relative (else absolute)
    116 	IsRelativeExpiration bool `json:"is_relative_expiration"`
    117 
    118 	// Whether or not this is a supplemental record
    119 	IsSupplemental bool `json:"is_supplemental"`
    120 
    121 	// Whether or not this is a shadow record
    122 	IsShadow bool `json:"is_shadow"`
    123 
    124 	// Whether or not this is a maintenance record
    125 	IsMaintenance bool `json:"is_maintenance"`
    126 }
    127 
    128 // See https://docs.gnunet.org/latest/developers/rest-api/namestore.html
    129 type NamestoreRecord struct {
    130 	// Name of the record set
    131 	RecordName string `json:"record_name"`
    132 
    133 	// The record set
    134 	Records []RecordData `json:"data"`
    135 }
    136 
    137 // Registrar is the primary object of the service
    138 type Registrar struct {
    139 
    140 	// The main router
    141 	Router *mux.Router
    142 
    143 	// Our configuration from the config.json
    144 	Cfg RegistrarConfig
    145 
    146 	// Logger
    147 	Logger *log.Logger
    148 
    149 	// Map of supported validators as defined in the configuration
    150 	Validators map[string]bool
    151 
    152 	// landing page
    153 	LandingTpl *template.Template
    154 
    155 	// name page
    156 	NameTpl *template.Template
    157 
    158 	// buy names page
    159 	BuyTpl *template.Template
    160 
    161 	// edit registration page
    162 	EditTpl *template.Template
    163 
    164 	// Merchant object
    165 	Merchant merchant.Merchant
    166 
    167 	// Relative record expiration (NOT registration expiration!)
    168 	RelativeDelegationExpiration time.Duration
    169 
    170 	// Registration expiration (NOT record expiration!)
    171 	RelativeRegistrationExpiration time.Duration
    172 
    173 	// Registration expiration days count
    174 	RegistrationExpirationDaysCount uint64
    175 
    176 	// Payment expiration (time you have to pay for registration)
    177 	PaymentExpiration time.Duration
    178 
    179 	// Name of our root zone
    180 	RootZoneName string
    181 
    182 	// Key of our root zone
    183 	RootZoneKey string
    184 
    185 	// Suggested suffix for our zone
    186 	SuffixHint string
    187 
    188 	// Gnunet REST API basename
    189 	GnunetUrl string
    190 
    191 	// Gnunet basic auth on/off
    192 	GnunetBasicAuthEnabled bool
    193 
    194 	// Gnunet basic auth
    195 	GnunetUsername string
    196 
    197 	// Gnunet basic auth
    198 	GnunetPassword string
    199 
    200 	// Registrar base URL
    201 	BaseUrl string
    202 
    203 	// The template to use for the summary string
    204 	SummaryTemplateString string
    205 
    206 	// Valid label regex
    207 	ValidLabelRegex string
    208 
    209 	// Valid label script
    210 	ValidLabelScript string
    211 
    212 	// Cost for a registration
    213 	RegistrationCost *talerutil.Amount
    214 
    215 	// Cost for a registration
    216 	CurrencySpec talerutil.CurrencySpecification
    217 }
    218 
    219 type VersionResponse struct {
    220 	// libtool-style representation of the Merchant protocol version, see
    221 	// https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
    222 	// The format is "current:revision:age".
    223 	Version string `json:"version"`
    224 }
    225 
    226 func (t *Registrar) configResponse(w http.ResponseWriter, r *http.Request) {
    227 	cfg := VersionResponse{
    228 		Version: "0:0:0",
    229 	}
    230 	w.Header().Set("Content-Type", "application/json")
    231 	response, err := json.Marshal(cfg)
    232 	if nil != err {
    233 		t.Logf(LogError, "%s\n", err.Error())
    234 		return
    235 	}
    236 	w.Write(response)
    237 }
    238 
    239 func generateRegistrationId() string {
    240 	return uuid.New().String()
    241 }
    242 
    243 func (t *Registrar) landingPage(w http.ResponseWriter, r *http.Request) {
    244 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    245 
    246 	fullData := map[string]any{
    247 		"suffixHint": t.SuffixHint,
    248 		"extZkey":    r.URL.Query().Get("zkey"),
    249 		"zoneKey":    t.RootZoneKey,
    250 		"version":    t.Cfg.Version,
    251 		"error":      r.URL.Query().Get("error"),
    252 	}
    253 	err := t.LandingTpl.Execute(w, fullData)
    254 	if err != nil {
    255 		t.Logf(LogError, "%s\n", err.Error())
    256 	}
    257 }
    258 
    259 func (t *Registrar) isNameValid(label string) (err error) {
    260 	if label == "@" {
    261 		return fmt.Errorf("'%s' invalid: '@' not allowed", label)
    262 	}
    263 	if strings.Contains(label, ".") {
    264 		return fmt.Errorf("'%s' invalid: '.' not allowed", label)
    265 	}
    266 	if t.ValidLabelRegex != "" {
    267 		matched, _ := regexp.MatchString(t.ValidLabelRegex, label)
    268 		if !matched {
    269 			return fmt.Errorf("label '%s' not allowed by policy", label)
    270 		}
    271 	}
    272 	if t.ValidLabelScript != "" {
    273 		path, err := exec.LookPath(t.ValidLabelScript)
    274 		if err != nil {
    275 			t.Logf(LogError, "%s\n", err.Error())
    276 			return errors.New("internal error")
    277 		}
    278 		_, err = exec.Command(path, label).Output()
    279 		if err != nil {
    280 			return fmt.Errorf("label '%s' not allowed by policy", label)
    281 		}
    282 	}
    283 	return
    284 }
    285 
    286 func (t *Registrar) searchPage(w http.ResponseWriter, r *http.Request) {
    287 	var (
    288 		label string
    289 		zkey  string
    290 		err   error
    291 	)
    292 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    293 	label = r.URL.Query().Get("label")
    294 	err = t.isNameValid(label)
    295 	if nil != err {
    296 		http.Redirect(w, r, fmt.Sprintf("/?error=%s", err), http.StatusSeeOther)
    297 		return
    298 	}
    299 	zkey = r.URL.Query().Get("zkey")
    300 	if zkey == "" {
    301 		http.Redirect(w, r, "/name/"+url.QueryEscape(label), http.StatusSeeOther)
    302 	} else {
    303 		http.Redirect(w, r, "/name/"+url.QueryEscape(label)+"?zkey="+url.QueryEscape(zkey), http.StatusSeeOther)
    304 	}
    305 }
    306 
    307 func (t *Registrar) expireRegistration(label string) (err error) {
    308 	var (
    309 		gnunetError GnunetError
    310 		client      *http.Client
    311 	)
    312 	client = &http.Client{}
    313 	req, _ := http.NewRequest(http.MethodDelete, t.GnunetUrl+"/namestore/"+t.RootZoneName+"/"+label, nil)
    314 	if t.GnunetBasicAuthEnabled {
    315 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    316 	}
    317 	resp, err := client.Do(req)
    318 	if err != nil {
    319 		return err
    320 	}
    321 	err = resp.Body.Close()
    322 	if err != nil {
    323 		return err
    324 	}
    325 	if http.StatusNotFound == resp.StatusCode {
    326 		return nil
    327 	}
    328 	if http.StatusNoContent != resp.StatusCode {
    329 		t.Logf(LogError, "Got error: %d\n", resp.StatusCode)
    330 		_ = json.NewDecoder(resp.Body).Decode(&gnunetError)
    331 		return errors.New("GNUnet REST API error: " + gnunetError.Description)
    332 	}
    333 	return nil
    334 }
    335 
    336 func (t *Registrar) createOrUpdateRegistration(nsRecord *NamestoreRecord) (err error) {
    337 	var gnunetError GnunetError
    338 	reqString, _ := json.Marshal(nsRecord)
    339 	client := &http.Client{}
    340 	req, _ := http.NewRequest(http.MethodPut, t.GnunetUrl+"/namestore/"+t.RootZoneName, bytes.NewBuffer(reqString))
    341 	if t.GnunetBasicAuthEnabled {
    342 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    343 	}
    344 	resp, err := client.Do(req)
    345 	if nil != err {
    346 		return err
    347 	}
    348 	err = resp.Body.Close()
    349 	if err != nil {
    350 		return err
    351 	}
    352 	if http.StatusNoContent != resp.StatusCode {
    353 		t.Logf(LogError, "Got error: %d\n", resp.StatusCode)
    354 		err = json.NewDecoder(resp.Body).Decode(&gnunetError)
    355 		if nil != err {
    356 			return errors.New("GNUnet REST API error: " + err.Error())
    357 		}
    358 		return errors.New("GNUnet REST API error: " + gnunetError.Description)
    359 	}
    360 	return nil
    361 }
    362 
    363 func getEndOfDay(day time.Time) time.Time {
    364 	return time.Date(day.Year(), day.Month(), day.Day(), 23, 59, 59, 0, day.Location())
    365 }
    366 
    367 func (t *Registrar) setupRegistrationMetadataBeforePayment(label string, zkey string, orderId string, paymentUntil time.Time, regId string) (err error) {
    368 	var (
    369 		namestoreRequest     NamestoreRecord
    370 		delegationRecord     RecordData
    371 		metadataRecord       RecordData
    372 		registrationMetadata RegistrationMetadata
    373 	)
    374 	delegationRecord.IsPrivate = true // Private until payment is through
    375 	delegationRecord.IsRelativeExpiration = true
    376 	delegationRecord.IsSupplemental = false
    377 	delegationRecord.IsMaintenance = false
    378 	delegationRecord.IsShadow = false
    379 	delegationRecord.RecordType = guessDelegationRecordType(zkey)
    380 	delegationRecord.RelativeExpiration = uint64(t.RelativeDelegationExpiration.Microseconds())
    381 	delegationRecord.Value = zkey
    382 	metadataRecord.IsPrivate = true
    383 	metadataRecord.IsRelativeExpiration = true
    384 	metadataRecord.IsSupplemental = false
    385 	metadataRecord.IsMaintenance = true
    386 	metadataRecord.IsShadow = false
    387 	metadataRecord.RecordType = "TXT" // FIXME use new recory type
    388 	metadataRecord.RelativeExpiration = uint64(t.RelativeDelegationExpiration.Microseconds())
    389 	registrationMetadata = RegistrationMetadata{
    390 		Paid:              false,
    391 		OrderID:           orderId,
    392 		NeedsPaymentUntil: paymentUntil,
    393 		RegistrationID:    regId,
    394 		Expiration:        uint64(getEndOfDay(time.Now()).UnixMicro()),
    395 	}
    396 	metadataRecordValue, err := json.Marshal(registrationMetadata)
    397 	if nil != err {
    398 		return err
    399 	}
    400 	metadataRecord.Value = string(metadataRecordValue)
    401 	namestoreRequest.RecordName = label
    402 	namestoreRequest.Records = []RecordData{delegationRecord, metadataRecord}
    403 	return t.createOrUpdateRegistration(&namestoreRequest)
    404 }
    405 
    406 func (t *Registrar) updateRegistration(w http.ResponseWriter, r *http.Request) {
    407 	var (
    408 		namestoreResponse NamestoreRecord
    409 		zkeyRecord        RecordData
    410 		metaRecord        RecordData
    411 		regMetadata       *RegistrationMetadata
    412 		client            *http.Client
    413 		token             string
    414 		zkey              string
    415 	)
    416 	vars := mux.Vars(r)
    417 	sanitizedLabel := url.QueryEscape(vars["label"])
    418 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    419 	client = &http.Client{}
    420 	req, _ := http.NewRequest(http.MethodGet, t.GnunetUrl+"/namestore/"+t.RootZoneName+"/"+sanitizedLabel+"?include_maintenance=yes", nil)
    421 	if t.GnunetBasicAuthEnabled {
    422 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    423 	}
    424 	resp, err := client.Do(req)
    425 	if err != nil {
    426 		http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    427 		t.Logf(LogError, "Failed to get zone contents\n")
    428 		return
    429 	}
    430 	defer resp.Body.Close()
    431 	if http.StatusOK == resp.StatusCode {
    432 		respData, err := io.ReadAll(resp.Body)
    433 		if err != nil {
    434 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    435 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    436 			return
    437 		}
    438 		err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse)
    439 		if err != nil {
    440 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    441 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    442 			return
    443 		}
    444 		regMetadata, err = t.getCurrentRegistrationMetadata(vars["label"], &namestoreResponse)
    445 		if err != nil {
    446 			http.Redirect(w, r, "/"+"?error=Failed to get registration metadata", http.StatusSeeOther)
    447 			t.Logf(LogError, "Failed to get registration metadata: `%s'\n", err.Error())
    448 			return
    449 		}
    450 	} else if http.StatusNotFound != resp.StatusCode {
    451 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Error determining zone status", http.StatusSeeOther)
    452 		return
    453 	}
    454 	if nil == regMetadata {
    455 		http.Redirect(w, r, "/name/"+sanitizedLabel, http.StatusSeeOther)
    456 		return
    457 	}
    458 	err = r.ParseForm()
    459 	if nil != err {
    460 		t.Logf(LogError, "Unable to parse form: `%s'\n", err.Error())
    461 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Form invalid", http.StatusSeeOther)
    462 		return
    463 	}
    464 	token = r.Form.Get("token")
    465 	zkey = r.Form.Get("zkey")
    466 	if regMetadata.RegistrationID != token {
    467 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Unauthorized", http.StatusSeeOther)
    468 		return
    469 	}
    470 	for _, record := range namestoreResponse.Records {
    471 		if isDelegationRecordType(record.RecordType) {
    472 			zkeyRecord = record
    473 		} else {
    474 			metaRecord = record
    475 		}
    476 	}
    477 	zkeyRecord.Value = zkey
    478 	zkeyRecord.RecordType = guessDelegationRecordType(zkey)
    479 	namestoreResponse.Records = []RecordData{metaRecord, zkeyRecord}
    480 	err = t.createOrUpdateRegistration(&namestoreResponse)
    481 	if nil != err {
    482 		t.Logf(LogError, "%s\n", err.Error())
    483 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Update: Internal error", http.StatusSeeOther)
    484 		return
    485 	}
    486 	http.Redirect(w, r, "/name/"+sanitizedLabel+"/edit?token="+token, http.StatusSeeOther)
    487 }
    488 
    489 func (t *Registrar) editRegistration(w http.ResponseWriter, r *http.Request) {
    490 	var (
    491 		namestoreResponse NamestoreRecord
    492 		regMetadata       *RegistrationMetadata
    493 		value             string
    494 		client            *http.Client
    495 	)
    496 	vars := mux.Vars(r)
    497 	sanitizedLabel := url.QueryEscape(vars["label"])
    498 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    499 	client = &http.Client{}
    500 	req, _ := http.NewRequest(http.MethodGet, t.GnunetUrl+"/namestore/"+t.RootZoneName+"/"+sanitizedLabel+"?include_maintenance=yes", nil)
    501 	if t.GnunetBasicAuthEnabled {
    502 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    503 	}
    504 	resp, err := client.Do(req)
    505 	if err != nil {
    506 		http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    507 		t.Logf(LogError, "Failed to get zone contents\n")
    508 		return
    509 	}
    510 	defer resp.Body.Close()
    511 	if http.StatusOK == resp.StatusCode {
    512 		respData, err := io.ReadAll(resp.Body)
    513 		if err != nil {
    514 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    515 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    516 			return
    517 		}
    518 		err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse)
    519 		if err != nil {
    520 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    521 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    522 			return
    523 		}
    524 		regMetadata, err = t.getCurrentRegistrationMetadata(vars["label"], &namestoreResponse)
    525 		if err != nil {
    526 			http.Redirect(w, r, "/"+"?error=Failed to get registration metadata", http.StatusSeeOther)
    527 			t.Logf(LogError, "Failed to get registration metadata: `%s'\n", err.Error())
    528 			return
    529 		}
    530 	} else if http.StatusNotFound != resp.StatusCode {
    531 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Error determining zone status", http.StatusSeeOther)
    532 		return
    533 	}
    534 	if nil == regMetadata {
    535 		http.Redirect(w, r, "/name/"+sanitizedLabel, http.StatusSeeOther)
    536 		return
    537 	}
    538 	if regMetadata.RegistrationID != r.URL.Query().Get("token") {
    539 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Unauthorized", http.StatusSeeOther)
    540 		return
    541 	}
    542 	if !regMetadata.Paid {
    543 		http.Redirect(w, r, "/name/"+sanitizedLabel+"/buy/payment?token="+regMetadata.RegistrationID, http.StatusSeeOther)
    544 		return
    545 	}
    546 	for _, record := range namestoreResponse.Records {
    547 		if isDelegationRecordType(record.RecordType) {
    548 			value = record.Value
    549 		}
    550 	}
    551 	registeredUntil := time.UnixMicro(int64(regMetadata.Expiration))
    552 	registeredUntilStr := registeredUntil.Format(time.DateTime)
    553 	remainingDays := int64(time.Until(registeredUntil).Hours() / 24)
    554 	extendedExpiration := time.UnixMicro(int64(regMetadata.Expiration)).Add(t.RelativeRegistrationExpiration).Format(time.DateTime)
    555 	cost, _ := t.RegistrationCost.FormatWithCurrencySpecification(t.CurrencySpec)
    556 	fullData := map[string]any{
    557 		"label":              vars["label"],
    558 		"zkey":               value,
    559 		"extendedExpiration": extendedExpiration,
    560 		"extensionDaysCount": t.RegistrationExpirationDaysCount,
    561 		"remainingDays":      remainingDays,
    562 		"registeredUntil":    registeredUntilStr,
    563 		"token":              r.URL.Query().Get("token"),
    564 		"version":            t.Cfg.Version,
    565 		"error":              r.URL.Query().Get("error"),
    566 		"cost":               cost,
    567 		"suffixHint":         t.SuffixHint,
    568 	}
    569 	err = t.EditTpl.Execute(w, fullData)
    570 	if err != nil {
    571 		t.Logf(LogError, "%s\n", err.Error())
    572 	}
    573 }
    574 
    575 func (t *Registrar) paymentPage(w http.ResponseWriter, r *http.Request) {
    576 	var (
    577 		namestoreResponse NamestoreRecord
    578 		regMetadata       *RegistrationMetadata
    579 		client            *http.Client
    580 		label             string
    581 		errorMsg          string
    582 	)
    583 	vars := mux.Vars(r)
    584 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    585 	client = &http.Client{}
    586 	label = vars["label"]
    587 	sanitizedLabel := url.QueryEscape(vars["label"])
    588 	err := t.isNameValid(label)
    589 	if nil != err {
    590 		http.Redirect(w, r, fmt.Sprintf("/?error=%s", err), http.StatusSeeOther)
    591 		return
    592 	}
    593 	req, _ := http.NewRequest(http.MethodGet, t.GnunetUrl+"/namestore/"+t.RootZoneName+"/"+sanitizedLabel+"?include_maintenance=yes", nil)
    594 	if t.GnunetBasicAuthEnabled {
    595 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    596 	}
    597 	resp, err := client.Do(req)
    598 	if err != nil {
    599 		http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    600 		t.Logf(LogError, "Failed to get zone contents\n")
    601 		return
    602 	}
    603 	defer resp.Body.Close()
    604 	if http.StatusOK == resp.StatusCode {
    605 		respData, err := io.ReadAll(resp.Body)
    606 		if err != nil {
    607 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    608 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    609 			return
    610 		}
    611 		err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse)
    612 		if err != nil {
    613 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    614 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    615 			return
    616 		}
    617 		regMetadata, err = t.getCurrentRegistrationMetadata(sanitizedLabel, &namestoreResponse)
    618 		if err != nil {
    619 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get registration metadata", http.StatusSeeOther)
    620 			t.Logf(LogError, "Failed to get registration metadata: `%s'\n", err.Error())
    621 			return
    622 		}
    623 	} else if http.StatusNotFound != resp.StatusCode {
    624 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Payment failed: Error determining zone status", http.StatusSeeOther)
    625 		return
    626 	}
    627 	if nil == regMetadata {
    628 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Payment failed: Please try again.", http.StatusSeeOther)
    629 		return
    630 	}
    631 	if regMetadata.Paid {
    632 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Payment already paid.", http.StatusSeeOther)
    633 		return
    634 	}
    635 	_, orderStatus, payto, paytoErr := t.Merchant.IsOrderPaid(regMetadata.OrderID)
    636 	if paytoErr != nil {
    637 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Payment failed: Error getting payment data", http.StatusSeeOther)
    638 		return
    639 	}
    640 	encodedPng := ""
    641 	if payto != "" {
    642 		qrPng, qrErr := qrcode.Encode(payto, qrcode.Medium, 256)
    643 		if qrErr != nil {
    644 			http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Error generating QR code", http.StatusSeeOther)
    645 			return
    646 		}
    647 		encodedPng = base64.StdEncoding.EncodeToString(qrPng)
    648 	}
    649 	cost, _ := t.RegistrationCost.FormatWithCurrencySpecification(t.CurrencySpec)
    650 	w.Header().Set("Refresh", "20;url="+t.BaseUrl+"/name/"+sanitizedLabel+"/edit?token="+regMetadata.RegistrationID)
    651 	fullData := map[string]interface{}{
    652 		"orderUnpaid":    merchant.OrderUnpaid == orderStatus,
    653 		"qrCode":         template.URL("data:image/png;base64," + encodedPng),
    654 		"payto":          template.URL(payto),
    655 		"fulfillmentUrl": template.URL(t.BaseUrl + "/name/" + sanitizedLabel + "/edit?token=" + regMetadata.RegistrationID),
    656 		"registrationId": regMetadata.RegistrationID,
    657 		"label":          sanitizedLabel,
    658 		"version":        t.Cfg.Version,
    659 		"error":          errorMsg,
    660 		"cost":           cost,
    661 		"suffixHint":     t.SuffixHint,
    662 	}
    663 	err = t.BuyTpl.Execute(w, fullData)
    664 	if err != nil {
    665 		t.Logf(LogError, "%s\n", err.Error())
    666 	}
    667 }
    668 
    669 func (t *Registrar) buyPage(w http.ResponseWriter, r *http.Request) {
    670 	var (
    671 		namestoreResponse NamestoreRecord
    672 		regMetadata       *RegistrationMetadata
    673 		client            *http.Client
    674 		label             string
    675 		regId             string
    676 	)
    677 	vars := mux.Vars(r)
    678 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    679 	client = &http.Client{}
    680 	label = vars["label"]
    681 	sanitizedLabel := url.QueryEscape(vars["label"])
    682 	err := t.isNameValid(label)
    683 	if nil != err {
    684 		http.Redirect(w, r, fmt.Sprintf("/?error=%s", err), http.StatusSeeOther)
    685 		return
    686 	}
    687 	req, _ := http.NewRequest(http.MethodGet, t.GnunetUrl+"/namestore/"+t.RootZoneName+"/"+sanitizedLabel+"?include_maintenance=yes", nil)
    688 	if t.GnunetBasicAuthEnabled {
    689 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    690 	}
    691 	resp, err := client.Do(req)
    692 	if err != nil {
    693 		http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    694 		t.Logf(LogError, "Failed to get zone contents\n")
    695 		return
    696 	}
    697 	defer resp.Body.Close()
    698 	if http.StatusOK == resp.StatusCode {
    699 		respData, err := io.ReadAll(resp.Body)
    700 		if err != nil {
    701 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    702 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    703 			return
    704 		}
    705 		err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse)
    706 		if err != nil {
    707 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get zone contents", http.StatusSeeOther)
    708 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    709 			return
    710 		}
    711 		regMetadata, err = t.getCurrentRegistrationMetadata(sanitizedLabel, &namestoreResponse)
    712 		if err != nil {
    713 			http.Redirect(w, r, "/"+"?error=Registration failed: Failed to get registration metadata", http.StatusSeeOther)
    714 			t.Logf(LogError, "Failed to get registration metadata: `%s'\n", err.Error())
    715 			return
    716 		}
    717 	} else if http.StatusNotFound != resp.StatusCode {
    718 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Error determining zone status", http.StatusSeeOther)
    719 		return
    720 	}
    721 	if nil != regMetadata {
    722 		if !regMetadata.Paid {
    723 			http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Pending buy order", http.StatusSeeOther)
    724 			return
    725 		}
    726 		regMetadata.Paid = false
    727 		regId = regMetadata.RegistrationID
    728 	} else {
    729 		regId = generateRegistrationId()
    730 	}
    731 	summaryMsg := strings.Replace(t.SummaryTemplateString, "${NAME}", label, 1)
    732 	orderID, newOrderErr := t.Merchant.AddNewOrder(*t.RegistrationCost, summaryMsg, t.BaseUrl+"/name/"+sanitizedLabel+"/edit?token="+regId)
    733 	if newOrderErr != nil {
    734 		t.Logf(LogError, "%s\n", newOrderErr.Error())
    735 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Unable to create order", http.StatusSeeOther)
    736 		return
    737 	}
    738 	// FIXME: based on order status, we probably want to display something else
    739 	_, _, _, paytoErr := t.Merchant.IsOrderPaid(orderID)
    740 	if paytoErr != nil {
    741 		http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Error getting payment data", http.StatusSeeOther)
    742 		return
    743 	}
    744 	paymentUntil := time.Now().Add(t.PaymentExpiration)
    745 	if nil != regMetadata {
    746 		var newZkeyRecord RecordData
    747 		var metaRecord RecordData
    748 		for _, record := range namestoreResponse.Records {
    749 			if isDelegationRecordType(record.RecordType) {
    750 				record.IsPrivate = false
    751 				newZkeyRecord = record
    752 			} else {
    753 				metaRecord = record
    754 			}
    755 		}
    756 		regMetadata.NeedsPaymentUntil = paymentUntil
    757 		regMetadata.OrderID = orderID
    758 		metadataRecordValue, err := json.Marshal(regMetadata)
    759 		if nil != err {
    760 			t.Logf(LogError, "%s\n", err.Error())
    761 			http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Internal error", http.StatusSeeOther)
    762 			return
    763 		}
    764 		metaRecord.Value = string(metadataRecordValue)
    765 		namestoreResponse.Records = []RecordData{metaRecord, newZkeyRecord}
    766 		err = t.createOrUpdateRegistration(&namestoreResponse)
    767 		if nil != err {
    768 			t.Logf(LogError, "%s\n", err.Error())
    769 			http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Internal error", http.StatusSeeOther)
    770 			return
    771 		}
    772 	} else {
    773 		err = t.setupRegistrationMetadataBeforePayment(sanitizedLabel, r.URL.Query().Get("zkey"), orderID, paymentUntil, regId)
    774 		if err != nil {
    775 			t.Logf(LogError, "%s\n", err.Error())
    776 			http.Redirect(w, r, "/name/"+sanitizedLabel+"?error=Registration failed: Internal error", http.StatusSeeOther)
    777 			return
    778 		}
    779 	}
    780 	http.Redirect(w, r, "/name/"+sanitizedLabel+"/buy/payment?token="+regId, http.StatusSeeOther)
    781 }
    782 
    783 func (t *Registrar) getCurrentRegistrationMetadata(label string, nsRecord *NamestoreRecord) (*RegistrationMetadata, error) {
    784 	var (
    785 		regMetadata  RegistrationMetadata
    786 		haveMetadata bool
    787 	)
    788 	haveMetadata = false
    789 	for _, record := range nsRecord.Records {
    790 		if record.RecordType == "TXT" {
    791 			err := json.Unmarshal([]byte(record.Value), &regMetadata)
    792 			if err != nil {
    793 				t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    794 				return nil, err
    795 			}
    796 			haveMetadata = true
    797 		}
    798 	}
    799 	if !haveMetadata {
    800 		return nil, nil
    801 	}
    802 	// Does this registration have an unpaid order? if yes, check payment status and update if necessary.
    803 	if !regMetadata.Paid {
    804 		rc, orderStatus, _, paytoErr := t.Merchant.IsOrderPaid(regMetadata.OrderID)
    805 		if nil != paytoErr {
    806 			if rc == http.StatusNotFound {
    807 				if time.Now().After(time.UnixMicro(int64(regMetadata.Expiration))) {
    808 					t.Logf(LogInfo, "Registration for `%s' not found, removing\n", label)
    809 					err := t.expireRegistration(label)
    810 					if nil != err {
    811 						t.Logf(LogInfo, "%s\n", err.Error())
    812 					}
    813 					return nil, nil
    814 				} else {
    815 					return &regMetadata, nil
    816 				}
    817 			}
    818 			return nil, errors.New("Error determining payment status: " + paytoErr.Error())
    819 		}
    820 		if merchant.OrderPaid == orderStatus {
    821 			// Order was paid!
    822 			regMetadata.Paid = true
    823 			var newZkeyRecord RecordData
    824 			var newMetaRecord RecordData
    825 			for _, record := range nsRecord.Records {
    826 				if isDelegationRecordType(record.RecordType) {
    827 					record.IsPrivate = false
    828 					newZkeyRecord = record
    829 				}
    830 				if record.RecordType == "TXT" {
    831 					metadataRecordValue, err := json.Marshal(regMetadata)
    832 					if nil != err {
    833 						return nil, err
    834 					}
    835 					record.Value = string(metadataRecordValue)
    836 					newMetaRecord = record
    837 				}
    838 			}
    839 			// Note how for every time the payment is completed, the registration duration increases
    840 			regMetadata.Expiration += uint64(t.RelativeRegistrationExpiration.Microseconds())
    841 			metadataRecordValue, err := json.Marshal(regMetadata)
    842 			if nil != err {
    843 				return nil, err
    844 			}
    845 			newMetaRecord.Value = string(metadataRecordValue)
    846 			nsRecord.Records = []RecordData{newMetaRecord, newZkeyRecord}
    847 			err = t.createOrUpdateRegistration(nsRecord)
    848 			if nil != err {
    849 				return nil, err
    850 			}
    851 			return &regMetadata, nil
    852 		} else {
    853 			// Remove metadata if payment limit exceeded and registration expired
    854 			if time.Now().After(regMetadata.NeedsPaymentUntil) && time.Now().After(time.UnixMicro(int64(regMetadata.Expiration))) {
    855 				t.Logf(LogDebug, "Payment request for `%s' has expired, removing\n", label)
    856 				err := t.expireRegistration(label)
    857 				if nil != err {
    858 					t.Logf(LogInfo, "%s\n", err.Error())
    859 				}
    860 				return nil, nil
    861 			}
    862 		}
    863 	} else {
    864 		if time.Now().After(time.UnixMicro(int64(regMetadata.Expiration))) {
    865 			t.Logf(LogDebug, "Registration for `%s' has expired, removing\n", label)
    866 			err := t.expireRegistration(label)
    867 			if nil != err {
    868 				t.Logf(LogInfo, "%s\n", err.Error())
    869 			}
    870 			return nil, nil
    871 		}
    872 	}
    873 	return &regMetadata, nil
    874 }
    875 
    876 func guessDelegationRecordType(val string) string {
    877 	if strings.HasPrefix(val, "000G00") {
    878 		return "PKEY"
    879 	} else {
    880 		return "EDKEY"
    881 	}
    882 }
    883 
    884 func isDelegationRecordType(typ string) bool {
    885 	return typ == "PKEY" || typ == "EDKEY"
    886 }
    887 
    888 func (t *Registrar) namePage(w http.ResponseWriter, r *http.Request) {
    889 	var (
    890 		namestoreResponse  NamestoreRecord
    891 		value              string
    892 		registeredUntilStr string
    893 		regMetadata        *RegistrationMetadata
    894 		remainingDays      int64
    895 		client             *http.Client
    896 		registered         = r.URL.Query().Get("registered") == "true"
    897 	)
    898 	vars := mux.Vars(r)
    899 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    900 	label := vars["label"]
    901 	err := t.isNameValid(label)
    902 	sanitizedLabel := url.QueryEscape(vars["label"])
    903 	if nil != err {
    904 		http.Redirect(w, r, fmt.Sprintf("/?error=%s", err), http.StatusSeeOther)
    905 		return
    906 	}
    907 	client = &http.Client{}
    908 	req, _ := http.NewRequest(http.MethodGet, t.GnunetUrl+"/namestore/"+t.RootZoneName+"/"+sanitizedLabel+"?include_maintenance=yes", nil)
    909 	if t.GnunetBasicAuthEnabled {
    910 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
    911 	}
    912 	resp, err := client.Do(req)
    913 	if err != nil {
    914 		http.Redirect(w, r, "/"+"?error=Failed to get zone contents.", http.StatusSeeOther)
    915 		t.Logf(LogError, "Failed to get zone contents\n")
    916 		return
    917 	}
    918 	defer resp.Body.Close()
    919 	if http.StatusOK == resp.StatusCode {
    920 		respData, err := io.ReadAll(resp.Body)
    921 		if err != nil {
    922 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    923 			http.Redirect(w, r, "/"+"?error=Failed to get zone contents.", http.StatusSeeOther)
    924 			return
    925 		}
    926 		err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse)
    927 		if err != nil {
    928 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
    929 			http.Redirect(w, r, "/"+"?error=Failed to get zone contents.", http.StatusSeeOther)
    930 			return
    931 		}
    932 		regMetadata, err = t.getCurrentRegistrationMetadata(sanitizedLabel, &namestoreResponse)
    933 		if err != nil {
    934 			t.Logf(LogError, "Failed to get registration metadata: `%s'\n", err.Error())
    935 			http.Redirect(w, r, "/"+"?error=Failed to get registration metadata.", http.StatusSeeOther)
    936 			return
    937 		}
    938 	} else if http.StatusNotFound != resp.StatusCode {
    939 		http.Redirect(w, r, "/"+"?error=Error retrieving zone information.", http.StatusSeeOther)
    940 		return
    941 	}
    942 	for _, record := range namestoreResponse.Records {
    943 		if isDelegationRecordType(record.RecordType) {
    944 			value = record.Value
    945 		}
    946 	}
    947 	if regMetadata != nil {
    948 		if time.Now().Before(time.UnixMicro(int64(regMetadata.Expiration))) {
    949 			registeredUntil := time.UnixMicro(int64(regMetadata.Expiration))
    950 			registeredUntilStr = registeredUntil.Format(time.DateTime)
    951 			remainingDays = int64(time.Until(registeredUntil).Hours() / 24)
    952 		}
    953 	}
    954 	cost, _ := t.RegistrationCost.FormatWithCurrencySpecification(t.CurrencySpec)
    955 	fullData := map[string]any{
    956 		"label":                 sanitizedLabel,
    957 		"version":               t.Cfg.Version,
    958 		"error":                 r.URL.Query().Get("error"),
    959 		"zkey":                  r.URL.Query().Get("zkey"),
    960 		"cost":                  cost,
    961 		"available":             regMetadata == nil,
    962 		"currentValue":          value,
    963 		"suffixHint":            t.SuffixHint,
    964 		"registrationDaysCount": t.RegistrationExpirationDaysCount,
    965 		"registeredUntil":       registeredUntilStr,
    966 		"remainingDays":         remainingDays,
    967 		"registrationSuccess":   registered,
    968 	}
    969 	err = t.NameTpl.Execute(w, fullData)
    970 	if err != nil {
    971 		t.Logf(LogError, "%s\n", err.Error())
    972 	}
    973 }
    974 
    975 type LogLevel int
    976 
    977 const (
    978 	LogError LogLevel = iota
    979 	LogWarning
    980 	LogInfo
    981 	LogDebug
    982 )
    983 
    984 var LoglevelStringMap = map[LogLevel]string{
    985 	LogDebug:   "DEBUG",
    986 	LogError:   "ERROR",
    987 	LogWarning: "WARN",
    988 	LogInfo:    "INFO",
    989 }
    990 
    991 func (t *Registrar) Logf(loglevel LogLevel, fmt string, args ...any) {
    992 	if loglevel > t.Cfg.Loglevel {
    993 		return
    994 	}
    995 	t.Logger.SetPrefix("taler-directory - " + LoglevelStringMap[loglevel] + " ")
    996 	t.Logger.Printf(fmt, args...)
    997 }
    998 
    999 func (t *Registrar) getFileName(relativeFileName string) string {
   1000 	_, err := os.Stat(relativeFileName)
   1001 	if errors.Is(err, os.ErrNotExist) {
   1002 		_, err := os.Stat(t.Cfg.Datahome + "/" + relativeFileName)
   1003 		if errors.Is(err, os.ErrNotExist) {
   1004 			t.Logf(LogError, "Tried fallback not found `%s'\n", t.Cfg.Datahome+"/"+relativeFileName)
   1005 			return ""
   1006 		}
   1007 		return t.Cfg.Datahome + "/" + relativeFileName
   1008 	}
   1009 	return relativeFileName
   1010 }
   1011 
   1012 func (t *Registrar) setupHandlers() {
   1013 	t.Router = mux.NewRouter().StrictSlash(true)
   1014 
   1015 	t.Router.HandleFunc("/", t.landingPage).Methods("GET")
   1016 	t.Router.HandleFunc("/name/{label}", t.namePage).Methods("GET")
   1017 	t.Router.HandleFunc("/name/{label}/buy", t.buyPage).Methods("GET")
   1018 	t.Router.HandleFunc("/name/{label}/buy/payment", t.paymentPage).Methods("GET")
   1019 	t.Router.HandleFunc("/name/{label}/edit", t.editRegistration).Methods("GET")
   1020 	t.Router.HandleFunc("/name/{label}/edit", t.updateRegistration).Methods("POST")
   1021 	t.Router.HandleFunc("/search", t.searchPage).Methods("GET")
   1022 
   1023 	/* ToS API */
   1024 	// t.Router.HandleFunc("/terms", t.termsResponse).Methods("GET")
   1025 	// t.Router.HandleFunc("/privacy", t.privacyResponse).Methods("GET")
   1026 
   1027 	/* Config API */
   1028 	t.Router.HandleFunc("/config", t.configResponse).Methods("GET")
   1029 
   1030 	/* Assets HTML */
   1031 	t.Router.PathPrefix("/css").Handler(http.StripPrefix("/css", http.FileServer(http.Dir(t.getFileName("static/css")))))
   1032 	t.Router.PathPrefix("/images").Handler(http.StripPrefix("/images", http.FileServer(http.Dir(t.getFileName("static/images")))))
   1033 }
   1034 
   1035 // Initialize the gnsregistrar instance with cfgfile
   1036 func (t *Registrar) Initialize(cfg RegistrarConfig) {
   1037 	var (
   1038 		identityResponse IdentityInfo
   1039 		err              error
   1040 	)
   1041 	t.Cfg = cfg
   1042 	t.Logger = log.New(os.Stdout, "gnunet-gns-registrar:", log.LstdFlags)
   1043 	if t.Cfg.Ini.Section("gns-registrar").Key("production").MustBool(false) {
   1044 		t.Logf(LogInfo, "Production mode enabled\n")
   1045 	}
   1046 	navTplFile := t.Cfg.Ini.Section("gns-registrar").Key("nav_template").MustString(t.getFileName("web/templates/nav.html"))
   1047 	footerTplFile := t.Cfg.Ini.Section("gns-registrar").Key("footer_template").MustString(t.getFileName("web/templates/footer.html"))
   1048 	landingTplFile := t.Cfg.Ini.Section("gns-registrar").Key("landing_template").MustString(t.getFileName("web/templates/landing.html"))
   1049 	t.LandingTpl, err = template.ParseFiles(landingTplFile, navTplFile, footerTplFile)
   1050 	if err != nil {
   1051 		t.Logf(LogError, "%s\n", err.Error())
   1052 		os.Exit(1)
   1053 	}
   1054 	nameTplFile := t.Cfg.Ini.Section("gns-registrar").Key("name_template").MustString(t.getFileName("web/templates/name.html"))
   1055 	t.NameTpl, err = template.ParseFiles(nameTplFile, navTplFile, footerTplFile)
   1056 	if err != nil {
   1057 		t.Logf(LogError, err.Error())
   1058 		os.Exit(1)
   1059 	}
   1060 	buyTplFile := t.Cfg.Ini.Section("gns-registrar").Key("buy_template").MustString(t.getFileName("web/templates/buy.html"))
   1061 	t.BuyTpl, err = template.ParseFiles(buyTplFile, navTplFile, footerTplFile)
   1062 	if err != nil {
   1063 		t.Logf(LogError, "%s\n", err.Error())
   1064 		os.Exit(1)
   1065 	}
   1066 	editTplFile := t.Cfg.Ini.Section("gns-registrar").Key("edit_template").MustString(t.getFileName("web/templates/edit.html"))
   1067 	t.EditTpl, err = template.ParseFiles(editTplFile, navTplFile, footerTplFile)
   1068 	if err != nil {
   1069 		t.Logf(LogError, "%s\n", err.Error())
   1070 		os.Exit(1)
   1071 	}
   1072 	paymentExp := t.Cfg.Ini.Section("gns-registrar").Key("payment_required_expiration").MustString("1h")
   1073 	recordExp := t.Cfg.Ini.Section("gns-registrar").Key("relative_delegation_expiration").MustString("24h")
   1074 	t.RegistrationExpirationDaysCount = t.Cfg.Ini.Section("gns-registrar").Key("registration_duration_days").MustUint64(5)
   1075 	t.RelativeRegistrationExpiration, _ = time.ParseDuration(fmt.Sprintf("%dh", t.RegistrationExpirationDaysCount*24))
   1076 	t.RelativeDelegationExpiration, _ = time.ParseDuration(recordExp)
   1077 	t.PaymentExpiration, _ = time.ParseDuration(paymentExp)
   1078 	costStr := t.Cfg.Ini.Section("gns-registrar").Key("registration_cost").MustString("KUDOS:0.3")
   1079 	t.RegistrationCost, err = talerutil.ParseAmount(costStr)
   1080 	if err != nil {
   1081 		t.Logf(LogError, "Error parsing amount `%s': `%s'\n", costStr, err.Error())
   1082 		os.Exit(1)
   1083 	}
   1084 	t.BaseUrl = t.Cfg.Ini.Section("gns-registrar").Key("base_url").MustString("http://localhost:11000")
   1085 	t.SuffixHint = t.Cfg.Ini.Section("gns-registrar").Key("suffix_hint").MustString("example.alt")
   1086 	t.SummaryTemplateString = t.Cfg.Ini.Section("gns-registrar").Key("order_summary_template").MustString("Registration of `${NAME}' at GNUnet FCFS registrar")
   1087 	t.RootZoneName = t.Cfg.Ini.Section("gns-registrar").Key("root_zone_name").MustString("test")
   1088 	t.GnunetUrl = t.Cfg.Ini.Section("gns-registrar").Key("base_url_gnunet").MustString("http://localhost:7776")
   1089 	t.GnunetBasicAuthEnabled = t.Cfg.Ini.Section("gns-registrar").Key("basic_auth_gnunet_enabled").MustBool(true)
   1090 	t.GnunetUsername = t.Cfg.Ini.Section("gns-registrar").Key("basic_auth_gnunet_username").MustString("jdoe")
   1091 	t.GnunetPassword = t.Cfg.Ini.Section("gns-registrar").Key("basic_auth_gnunet_password").MustString("secret")
   1092 	t.ValidLabelRegex = t.Cfg.Ini.Section("gns-registrar").Key("valid_label_regex").MustString("")
   1093 	t.ValidLabelScript = t.Cfg.Ini.Section("gns-registrar").Key("valid_label_script").MustString("")
   1094 	client := &http.Client{}
   1095 	req, _ := http.NewRequest(http.MethodGet, t.GnunetUrl+"/identity/name/"+t.RootZoneName, nil)
   1096 	if t.GnunetBasicAuthEnabled {
   1097 		req.SetBasicAuth(t.GnunetUsername, t.GnunetPassword)
   1098 	}
   1099 	resp, err := client.Do(req)
   1100 	if err != nil {
   1101 		t.Logf(LogError, "Failed to get zone key. Is gnunet running?\n")
   1102 		os.Exit(1)
   1103 		return
   1104 	}
   1105 	defer resp.Body.Close()
   1106 	if http.StatusNotFound == resp.StatusCode {
   1107 		t.Logf(LogError, "Zone not found.\n")
   1108 		os.Exit(1)
   1109 	} else if http.StatusOK == resp.StatusCode {
   1110 		respData, err := io.ReadAll(resp.Body)
   1111 		if err != nil {
   1112 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
   1113 			os.Exit(1)
   1114 		}
   1115 		err = json.NewDecoder(bytes.NewReader(respData)).Decode(&identityResponse)
   1116 		if err != nil {
   1117 			t.Logf(LogError, "Failed to get zone contents: `%s'\n", err.Error())
   1118 			os.Exit(1)
   1119 		}
   1120 		t.RootZoneKey = identityResponse.Pubkey
   1121 	} else {
   1122 		t.Logf(LogError, "Failed to get zone contents\n")
   1123 		os.Exit(1)
   1124 	}
   1125 	t.Merchant = cfg.Merchant
   1126 	merchConfig, err := t.Merchant.GetConfig()
   1127 	if nil != err {
   1128 		t.Logf(LogError, "Failed to get merchant config\n")
   1129 		os.Exit(1)
   1130 	}
   1131 	currencySpec, currencySupported := merchConfig.Currencies[t.RegistrationCost.Currency]
   1132 	for !currencySupported {
   1133 		t.Logf(LogError, "Currency `%s' not supported by merchant!\n", t.RegistrationCost.Currency)
   1134 		os.Exit(1)
   1135 	}
   1136 	t.CurrencySpec = currencySpec
   1137 	t.setupHandlers()
   1138 }