taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

commit ebfa3f498c8cca2ab0fb95b8d6e837a9c6aa83c7
parent 432910f4614aefe13788398610e84e8d54da8759
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Mon, 28 Apr 2025 10:49:56 +0200

add getopt and logging

Diffstat:
Mcmd/taldir-server/main.go | 33+++++++++++++++++++++++++++++++--
Mgo.mod | 1+
Mgo.sum | 2++
Mpkg/rest/taldir.go | 111+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
4 files changed, 103 insertions(+), 44 deletions(-)

diff --git a/cmd/taldir-server/main.go b/cmd/taldir-server/main.go @@ -35,6 +35,7 @@ import ( "gopkg.in/ini.v1" "gorm.io/driver/postgres" + "rsc.io/getopt" taldir "taler.net/taldir/pkg/rest" ) @@ -42,21 +43,48 @@ var ( t taldir.Taldir version string taldirdatahome string + verbose bool // FIXME do something with this? ) func handleRequests(t *taldir.Taldir) { log.Fatal(http.ListenAndServe(t.Cfg.Ini.Section("taldir").Key("bind_to").MustString("localhost:11000"), t.Router)) } +func printHelp() { + fmt.Print("taler-directory\n\n") + getopt.PrintDefaults() + fmt.Print("\nReport bugs to gnunet-developers@gnu.org.\n", + "Home page: https://taler.net\n", + "General help using GNU software: http://www.gnu.org/gethelp/\n") +} + func main() { var cfgFlag = flag.String("c", "", "Configuration file to use") + getopt.Alias("c", "config") + // FIXME use flags + loglevelStringOpt := flag.String("L", "INFO", "Log level to use. DEBUG, INFO, WARNING or ERROR") + getopt.Alias("L", "loglevel") + var verboseFlag = flag.Bool("v", false, "Verbose") + getopt.Alias("v", "verbose") + var helpFlag = flag.Bool("h", false, "Print help") + getopt.Alias("h", "help") - flag.Parse() - log.Println(version) + getopt.Parse() cfgfile := "taldir.conf" if len(*cfgFlag) != 0 { cfgfile = *cfgFlag } + if *helpFlag { + printHelp() + return + } + verbose = *verboseFlag + loglevel := taldir.LogInfo + for loglevelNum, loglevelString := range taldir.LoglevelStringMap { + if loglevelString == *loglevelStringOpt { + loglevel = loglevelNum + } + } t := taldir.Taldir{} cfg, err := ini.LooseLoad(cfgfile) if err != nil { @@ -76,6 +104,7 @@ func main() { Version: version, Datahome: taldirdatahome, Db: db, + Loglevel: loglevel, }) handleRequests(&t) } diff --git a/go.mod b/go.mod @@ -12,6 +12,7 @@ require ( gorm.io/driver/postgres v1.5.11 gorm.io/driver/sqlite v1.5.7 gorm.io/gorm v1.26.0 + rsc.io/getopt v0.0.0-20170811000552-20be20937449 ) require ( diff --git a/go.sum b/go.sum @@ -59,3 +59,5 @@ gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs= gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +rsc.io/getopt v0.0.0-20170811000552-20be20937449 h1:UukjJOsjQH0DIuyyrcod6CXHS6cdaMMuJmrt+SN1j4A= +rsc.io/getopt v0.0.0-20170811000552-20be20937449/go.mod h1:dhCdeqAxkyt5u3/sKRkUXuHaMXUu1Pt13GTQAM2xnig= diff --git a/pkg/rest/taldir.go b/pkg/rest/taldir.go @@ -68,6 +68,9 @@ type TaldirConfig struct { // The merchant connection to use Merchant merchant.Merchant + + // The loglevel to use + Loglevel LogLevel } // Taldir is the primary object of the Taldir service @@ -138,6 +141,9 @@ type Taldir struct { // I18n I18n *i18n.I18n + + // Logger + Logger *log.Logger } type ValidatorType string @@ -339,18 +345,17 @@ func (t *Taldir) isPMSValid(pms string) (err error) { if t.ValidPMSRegex != "" { matched, _ := regexp.MatchString(t.ValidPMSRegex, pms) if !matched { - return errors.New(fmt.Sprintf("Payment System Address `%s' invalid", pms)) // TODO i18n + return fmt.Errorf("payment System Address `%s' invalid", pms) // TODO i18n } } return } func (v *Validator) isAliasValid(alias string) (err error) { - log.Println(v.ValidAliasRegex) if v.ValidAliasRegex != "" { matched, _ := regexp.MatchString(v.ValidAliasRegex, alias) if !matched { - return errors.New(fmt.Sprintf("Alias `%s' invalid", alias)) // TODO i18n + return fmt.Errorf("alias `%s' invalid", alias) // TODO i18n } } return @@ -415,20 +420,20 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) { return } } else { - log.Println("New solution timeframe set.") + t.Logf(LogDebug, "New solution timeframe set.") validation.LastSolutionTimeframeStart = time.Now() validation.SolutionAttemptCount = 1 } t.Db.Save(&validation) expectedSolution := util.GenerateSolution(validation.TargetUri, validation.Challenge) - log.Printf("Expected solution: `%s', given: `%s'\n", expectedSolution, confirm.Solution) + t.Logf(LogDebug, "Expected solution: `%s', given: `%s'\n", expectedSolution, confirm.Solution) if confirm.Solution != expectedSolution { w.WriteHeader(http.StatusForbidden) return } err = t.Db.Delete(&validation).Error if err != nil { - log.Println("Error deleting validation") + t.Logf(LogError, "Error deleting validation") w.WriteHeader(http.StatusInternalServerError) return } @@ -439,7 +444,7 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) { err = t.Db.First(&entry, "hs_address = ?", entry.HsAddress).Error if err == nil { if validation.TargetUri == "" { - log.Printf("Deleted entry for '%s´\n", entry.HsAddress) + t.Logf(LogDebug, "Deleted entry for '%s´\n", entry.HsAddress) err = t.Db.Delete(&entry).Error if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -450,7 +455,7 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) { } } else { if validation.TargetUri == "" { - log.Printf("Validated a deletion request but no entry found for `%s'\n", entry.HsAddress) + t.Logf(LogWarning, "Validated a deletion request but no entry found for `%s'\n", entry.HsAddress) } else { err = t.Db.Create(&entry).Error if err != nil { @@ -544,7 +549,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { } rateLimited, err := t.isRateLimited(hAddress) if nil != err { - log.Printf("Error checking rate limit! %v", err) + t.Logf(LogError, "Error checking rate limit! %v", err) w.WriteHeader(http.StatusInternalServerError) return } else if rateLimited { @@ -602,7 +607,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { if paytoErr != nil { fmt.Println(paytoErr) w.WriteHeader(http.StatusInternalServerError) - log.Println(paytoErr) + t.Logf(LogError, paytoErr.Error()+"\n") return } if len(payto) != 0 { @@ -615,23 +620,23 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { } err = t.Db.Save(&validation).Error if err != nil { - log.Println(err) + t.Logf(LogError, err.Error()+"\n") w.WriteHeader(500) return } path, err := exec.LookPath(validator.Command) if err != nil { - log.Println(err) + t.Logf(LogError, err.Error()+"\n") t.Db.Delete(&validation) w.WriteHeader(500) return } - log.Printf("Found `%s' in path as `%s'\n", validator.Command, path) + t.Logf(LogDebug, "Found `%s' in path as `%s'\n", validator.Command, path) topic := t.I18n.GetLocale(r).GetMessage("taldirRegTopic") link := t.Host + "/register/" + url.QueryEscape(validation.HAddress) + "/" + url.QueryEscape(validation.Challenge) + "?address=" + url.QueryEscape(req.Address) message := t.I18n.GetLocale(r).GetMessage("taldirRegMessage", link) out, err := exec.Command(path, req.Address, validation.Challenge, topic, message).Output() - log.Printf("Executing `%s %s %s %s %s`\n", path, req.Address, validation.Challenge, topic, message) + t.Logf(LogDebug, "Executing `%s %s %s %s %s`\n", path, req.Address, validation.Challenge, topic, message) if err != nil { fmt.Printf("%s, %v\n", out, err) t.Db.Delete(&validation) @@ -679,7 +684,7 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) { return } if vars["challenge"] != validation.Challenge { - log.Println("Solution does not match challenge!") + t.Logf(LogWarning, "Solution does not match challenge!\n") w.WriteHeader(400) return } @@ -696,14 +701,14 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) { expectedHAddress := util.Base32CrockfordEncode(h.Sum(nil)) if expectedHAddress != validation.HAddress { - log.Println("Address does not match challenge!") + t.Logf(LogWarning, "Address does not match challenge!\n") w.WriteHeader(400) return } // FIXME: This is kind of broken and probably requires wallet support/integration first if validation.RequiresPayment { - log.Println("Validation requires payment") + t.Logf(LogWarning, "Validation requires payment\n") walletLink = "taler://taldir/" + vars["h_address"] + "/" + vars["challenge"] + "-wallet" png, err = qrcode.Encode(walletLink, qrcode.Medium, 256) if err != nil { @@ -772,7 +777,7 @@ func (t *Taldir) privacyResponse(w http.ResponseWriter, r *http.Request) { func (t *Taldir) landingPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") translateFunc := t.I18n.GetLocale(r).GetMessage - fullData := map[string]interface{}{ + fullData := map[string]any{ "validators": t.Validators, "version": t.Cfg.Version, "lookupOrRegisterCardTitle": template.HTML(translateFunc("lookup")), @@ -786,13 +791,12 @@ func (t *Taldir) landingPage(w http.ResponseWriter, r *http.Request) { if err != nil { fmt.Println(err) } - return } func (t *Taldir) imprintPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") translateFunc := t.I18n.GetLocale(r).GetMessage - fullData := map[string]interface{}{ + fullData := map[string]any{ "validators": t.Validators, "version": t.Cfg.Version, "productDisclaimerShort": template.HTML(translateFunc("productDisclaimerShort")), @@ -803,13 +807,12 @@ func (t *Taldir) imprintPage(w http.ResponseWriter, r *http.Request) { if err != nil { fmt.Println(err) } - return } func (t *Taldir) aboutPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") translateFunc := t.I18n.GetLocale(r).GetMessage - fullData := map[string]interface{}{ + fullData := map[string]any{ "validators": t.Validators, "version": t.Cfg.Version, "productDisclaimerShort": template.HTML(translateFunc("productDisclaimerShort")), @@ -821,7 +824,6 @@ func (t *Taldir) aboutPage(w http.ResponseWriter, r *http.Request) { if err != nil { fmt.Println(err) } - return } func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) { @@ -842,7 +844,7 @@ func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) emsg := "" found := false if nil != err { - log.Printf("Not a valid alias\n") + t.Logf(LogWarning, "Not a valid alias\n") emsg = t.I18n.GetLocale(r).GetMessage("aliasInvalid", alias) http.Redirect(w, r, fmt.Sprintf("/landing/"+val.Name+"?error=%s", emsg), http.StatusSeeOther) return @@ -852,12 +854,12 @@ func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) hsAddress := saltHAddress(hAddress, t.Salt) err = t.Db.First(&entry, "hs_address = ?", hsAddress).Error if err != nil { - log.Printf("`%s` not found.\n", hAddress) + t.Logf(LogError, "`%s` not found.\n", hAddress) } else { found = true } } - fullData := map[string]interface{}{ + fullData := map[string]any{ "version": t.Cfg.Version, "available": !found, "method": val.Name, @@ -940,7 +942,7 @@ func (t *Taldir) getFileName(relativeFileName string) string { if errors.Is(err, os.ErrNotExist) { _, err := os.Stat(t.Cfg.Datahome + "/" + relativeFileName) if errors.Is(err, os.ErrNotExist) { - log.Printf("Tried fallback not found %s\n", t.Cfg.Datahome+"/"+relativeFileName) + t.Logf(LogError, "Tried fallback not found %s\n", t.Cfg.Datahome+"/"+relativeFileName) return "" } return t.Cfg.Datahome + "/" + relativeFileName @@ -948,9 +950,34 @@ func (t *Taldir) getFileName(relativeFileName string) string { return relativeFileName } +type LogLevel int + +const ( + LogError LogLevel = iota + LogWarning + LogInfo + LogDebug +) + +var LoglevelStringMap = map[LogLevel]string{ + LogDebug: "DEBUG", + LogError: "ERROR", + LogWarning: "WARN", + LogInfo: "INFO", +} + +func (t *Taldir) Logf(loglevel LogLevel, fmt string, args ...any) { + if loglevel < t.Cfg.Loglevel { + return + } + t.Logger.SetPrefix("taler-directory - " + LoglevelStringMap[loglevel] + " ") + t.Logger.Printf(fmt, args...) +} + // Initialize the Taldir instance with cfgfile func (t *Taldir) Initialize(cfg TaldirConfig) { t.Cfg = cfg + t.Logger = log.New(os.Stdout, "taler-directory:", log.LstdFlags) // FIXME localedir i18n, err := i18n.New(i18n.Glob("./locales/*/*", i18n.LoaderConfig{ // Set custom functions per locale! @@ -973,14 +1000,14 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { continue } if !sec.HasKey("type") { - log.Printf("`type` key in section `[%s]` not found, disabling validator.\n", sec.Name()) + t.Logger.Printf("`type` key in section `[%s]` not found, disabling validator.\n", sec.Name()) continue } vname := strings.TrimPrefix(sec.Name(), "taldir-validator-") vlandingPageTplFile := sec.Key("registration_page").MustString(t.getFileName("web/templates/landing_" + vname + ".html")) vlandingPageTpl, err := template.ParseFiles(vlandingPageTplFile, navTplFile, footerTplFile) if err != nil { - log.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname) + t.Logger.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname) continue } t.Validators[vname] = Validator{ @@ -1001,14 +1028,14 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { t.ValidPMSRegex = cfg.Ini.Section("taldir").Key("valid_payment_system_address_regex").MustString(".*") t.ValidationTimeframe, err = time.ParseDuration(validationTTLStr) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } retryTimeframeStr := cfg.Ini.Section("taldir").Key("solution_attempt_timeframe").MustString("1h") t.SolutionTimeframe, err = time.ParseDuration(retryTimeframeStr) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } t.MonthlyFee = cfg.Ini.Section("taldir").Key("monthly_fee").MustString("KUDOS:0") @@ -1027,52 +1054,52 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { panic(err) } if cfg.Ini.Section("taldir").Key("purge_mappings_on_startup_dangerous").MustBool(false) { - log.Println("DANGER Purging mappings!") + t.Logger.Println("DANGER Purging mappings!") tx := t.Db.Where("1 = 1").Delete(&Entry{}) - log.Printf("Deleted %d entries.\n", tx.RowsAffected) + t.Logger.Printf("Deleted %d entries.\n", tx.RowsAffected) } // Clean up validations validationExpStr := cfg.Ini.Section("taldir").Key("validation_expiration").MustString("24h") validationExp, err := time.ParseDuration(validationExpStr) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } go func() { for { tx := t.Db.Where("created_at < ?", time.Now().Add(-validationExp)).Delete(&Validation{}) - log.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected) + t.Logger.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected) time.Sleep(validationExp) } }() imprintTplFile := cfg.Ini.Section("taldir").Key("imprint_page").MustString(t.getFileName("web/templates/imprint.html")) t.ImprintTpl, err = template.ParseFiles(imprintTplFile, navTplFile, footerTplFile) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } validationLandingTplFile := cfg.Ini.Section("taldir").Key("validation_landing").MustString(t.getFileName("web/templates/validation_landing.html")) t.ValidationTpl, err = template.ParseFiles(validationLandingTplFile, navTplFile, footerTplFile) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } landingTplFile := cfg.Ini.Section("taldir").Key("landing_page").MustString(t.getFileName("web/templates/landing.html")) t.LandingPageTpl, err = template.ParseFiles(landingTplFile, navTplFile, footerTplFile) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } lookupResultTplFile := cfg.Ini.Section("taldir").Key("lookup_result_page").MustString(t.getFileName("web/templates/lookup_result.html")) t.LookupResultPageTpl, err = template.ParseFiles(lookupResultTplFile, navTplFile, footerTplFile) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } aboutTplFile := cfg.Ini.Section("taldir").Key("about_page").MustString(t.getFileName("web/templates/about.html")) t.AboutPageTpl, err = template.ParseFiles(aboutTplFile, navTplFile, footerTplFile) if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } t.Salt = os.Getenv("TALDIR_SALT") @@ -1084,12 +1111,12 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { registrationCost, _ := talerutil.ParseAmount(t.MonthlyFee) merchConfig, err := t.Merchant.GetConfig() if err != nil { - log.Fatal(err) + t.Logger.Fatal(err) os.Exit(1) } currencySpec, currencySupported := merchConfig.Currencies[registrationCost.Currency] for !currencySupported { - log.Fatalf("Currency `%s' not supported by merchant!\n", registrationCost.Currency) + t.Logger.Fatalf("Currency `%s' not supported by merchant!\n", registrationCost.Currency) os.Exit(1) } t.CurrencySpec = currencySpec