helpers.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. package exprhelpers
  2. import (
  3. "bufio"
  4. "encoding/base64"
  5. "fmt"
  6. "net"
  7. "net/url"
  8. "os"
  9. "path"
  10. "reflect"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/antonmedv/expr"
  16. "github.com/bluele/gcache"
  17. "github.com/c-robinson/iplib"
  18. "github.com/cespare/xxhash/v2"
  19. "github.com/davecgh/go-spew/spew"
  20. "github.com/prometheus/client_golang/prometheus"
  21. log "github.com/sirupsen/logrus"
  22. "github.com/umahmood/haversine"
  23. "github.com/wasilibs/go-re2"
  24. "github.com/crowdsecurity/go-cs-lib/pkg/ptr"
  25. "github.com/crowdsecurity/crowdsec/pkg/cache"
  26. "github.com/crowdsecurity/crowdsec/pkg/database"
  27. "github.com/crowdsecurity/crowdsec/pkg/fflag"
  28. "github.com/crowdsecurity/crowdsec/pkg/types"
  29. )
  30. var dataFile map[string][]string
  31. var dataFileRegex map[string][]*regexp.Regexp
  32. var dataFileRe2 map[string][]*re2.Regexp
  33. // This is used to (optionally) cache regexp results for RegexpInFile operations
  34. var dataFileRegexCache map[string]gcache.Cache = make(map[string]gcache.Cache)
  35. /*prometheus*/
  36. var RegexpCacheMetrics = prometheus.NewGaugeVec(
  37. prometheus.GaugeOpts{
  38. Name: "cs_regexp_cache_size",
  39. Help: "Entries per regexp cache.",
  40. },
  41. []string{"name"},
  42. )
  43. var dbClient *database.Client
  44. var exprFunctionOptions []expr.Option
  45. var keyValuePattern = regexp.MustCompile(`(?P<key>[^=\s]+)=(?:"(?P<quoted_value>[^"\\]*(?:\\.[^"\\]*)*)"|(?P<value>[^=\s]+)|\s*)`)
  46. func GetExprOptions(ctx map[string]interface{}) []expr.Option {
  47. ret := []expr.Option{}
  48. ret = append(ret, exprFunctionOptions...)
  49. ret = append(ret, expr.Env(ctx))
  50. return ret
  51. }
  52. func Init(databaseClient *database.Client) error {
  53. dataFile = make(map[string][]string)
  54. dataFileRegex = make(map[string][]*regexp.Regexp)
  55. dataFileRe2 = make(map[string][]*re2.Regexp)
  56. dbClient = databaseClient
  57. exprFunctionOptions = []expr.Option{}
  58. for _, function := range exprFuncs {
  59. exprFunctionOptions = append(exprFunctionOptions,
  60. expr.Function(function.name,
  61. function.function,
  62. function.signature...,
  63. ))
  64. }
  65. return nil
  66. }
  67. func RegexpCacheInit(filename string, CacheCfg types.DataSource) error {
  68. //cache is explicitly disabled
  69. if CacheCfg.Cache != nil && !*CacheCfg.Cache {
  70. return nil
  71. }
  72. //cache is implicitly disabled if no cache config is provided
  73. if CacheCfg.Strategy == nil && CacheCfg.TTL == nil && CacheCfg.Size == nil {
  74. return nil
  75. }
  76. //cache is enabled
  77. if CacheCfg.Size == nil {
  78. CacheCfg.Size = ptr.Of(50)
  79. }
  80. gc := gcache.New(*CacheCfg.Size)
  81. if CacheCfg.Strategy == nil {
  82. CacheCfg.Strategy = ptr.Of("LRU")
  83. }
  84. switch *CacheCfg.Strategy {
  85. case "LRU":
  86. gc = gc.LRU()
  87. case "LFU":
  88. gc = gc.LFU()
  89. case "ARC":
  90. gc = gc.ARC()
  91. default:
  92. return fmt.Errorf("unknown cache strategy '%s'", *CacheCfg.Strategy)
  93. }
  94. if CacheCfg.TTL != nil {
  95. gc.Expiration(*CacheCfg.TTL)
  96. }
  97. cache := gc.Build()
  98. dataFileRegexCache[filename] = cache
  99. return nil
  100. }
  101. // UpdateCacheMetrics is called directly by the prom handler
  102. func UpdateRegexpCacheMetrics() {
  103. RegexpCacheMetrics.Reset()
  104. for name := range dataFileRegexCache {
  105. RegexpCacheMetrics.With(prometheus.Labels{"name": name}).Set(float64(dataFileRegexCache[name].Len(true)))
  106. }
  107. }
  108. func FileInit(fileFolder string, filename string, fileType string) error {
  109. log.Debugf("init (folder:%s) (file:%s) (type:%s)", fileFolder, filename, fileType)
  110. filepath := path.Join(fileFolder, filename)
  111. file, err := os.Open(filepath)
  112. if err != nil {
  113. return err
  114. }
  115. defer file.Close()
  116. if fileType == "" {
  117. log.Debugf("ignored file %s%s because no type specified", fileFolder, filename)
  118. return nil
  119. }
  120. if _, ok := dataFile[filename]; !ok {
  121. dataFile[filename] = []string{}
  122. }
  123. scanner := bufio.NewScanner(file)
  124. for scanner.Scan() {
  125. if strings.HasPrefix(scanner.Text(), "#") { // allow comments
  126. continue
  127. }
  128. if len(scanner.Text()) == 0 { //skip empty lines
  129. continue
  130. }
  131. switch fileType {
  132. case "regex", "regexp":
  133. if fflag.Re2RegexpInfileSupport.IsEnabled() {
  134. dataFileRe2[filename] = append(dataFileRe2[filename], re2.MustCompile(scanner.Text()))
  135. } else {
  136. dataFileRegex[filename] = append(dataFileRegex[filename], regexp.MustCompile(scanner.Text()))
  137. }
  138. case "string":
  139. dataFile[filename] = append(dataFile[filename], scanner.Text())
  140. default:
  141. return fmt.Errorf("unknown data type '%s' for : '%s'", fileType, filename)
  142. }
  143. }
  144. if err := scanner.Err(); err != nil {
  145. return err
  146. }
  147. return nil
  148. }
  149. // Expr helpers
  150. func Distinct(params ...any) (any, error) {
  151. if rt := reflect.TypeOf(params[0]).Kind(); rt != reflect.Slice && rt != reflect.Array {
  152. return nil, nil
  153. }
  154. array := params[0].([]interface{})
  155. if array == nil {
  156. return nil, nil
  157. }
  158. var exists map[any]bool = make(map[any]bool)
  159. var ret []interface{} = make([]interface{}, 0)
  160. for _, val := range array {
  161. if _, ok := exists[val]; !ok {
  162. exists[val] = true
  163. ret = append(ret, val)
  164. }
  165. }
  166. return ret, nil
  167. }
  168. func FlattenDistinct(params ...any) (any, error) {
  169. return Distinct(flatten(nil, reflect.ValueOf(params)))
  170. }
  171. func Flatten(params ...any) (any, error) {
  172. return flatten(nil, reflect.ValueOf(params)), nil
  173. }
  174. func flatten(args []interface{}, v reflect.Value) []interface{} {
  175. if v.Kind() == reflect.Interface {
  176. v = v.Elem()
  177. }
  178. if v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
  179. for i := 0; i < v.Len(); i++ {
  180. args = flatten(args, v.Index(i))
  181. }
  182. } else {
  183. args = append(args, v.Interface())
  184. }
  185. return args
  186. }
  187. // func Get(arr []string, index int) string {
  188. func Get(params ...any) (any, error) {
  189. arr := params[0].([]string)
  190. index := params[1].(int)
  191. if index >= len(arr) {
  192. return "", nil
  193. }
  194. return arr[index], nil
  195. }
  196. // func Atof(x string) float64 {
  197. func Atof(params ...any) (any, error) {
  198. x := params[0].(string)
  199. log.Debugf("debug atof %s", x)
  200. ret, err := strconv.ParseFloat(x, 64)
  201. if err != nil {
  202. log.Warningf("Atof : can't convert float '%s' : %v", x, err)
  203. }
  204. return ret, nil
  205. }
  206. // func Upper(s string) string {
  207. func Upper(params ...any) (any, error) {
  208. s := params[0].(string)
  209. return strings.ToUpper(s), nil
  210. }
  211. // func Lower(s string) string {
  212. func Lower(params ...any) (any, error) {
  213. s := params[0].(string)
  214. return strings.ToLower(s), nil
  215. }
  216. // func Distance(lat1 string, long1 string, lat2 string, long2 string) (float64, error) {
  217. func Distance(params ...any) (any, error) {
  218. lat1 := params[0].(string)
  219. long1 := params[1].(string)
  220. lat2 := params[2].(string)
  221. long2 := params[3].(string)
  222. lat1f, err := strconv.ParseFloat(lat1, 64)
  223. if err != nil {
  224. log.Warningf("lat1 is not a float : %v", err)
  225. return 0.0, fmt.Errorf("lat1 is not a float : %v", err)
  226. }
  227. long1f, err := strconv.ParseFloat(long1, 64)
  228. if err != nil {
  229. log.Warningf("long1 is not a float : %v", err)
  230. return 0.0, fmt.Errorf("long1 is not a float : %v", err)
  231. }
  232. lat2f, err := strconv.ParseFloat(lat2, 64)
  233. if err != nil {
  234. log.Warningf("lat2 is not a float : %v", err)
  235. return 0.0, fmt.Errorf("lat2 is not a float : %v", err)
  236. }
  237. long2f, err := strconv.ParseFloat(long2, 64)
  238. if err != nil {
  239. log.Warningf("long2 is not a float : %v", err)
  240. return 0.0, fmt.Errorf("long2 is not a float : %v", err)
  241. }
  242. //either set of coordinates is 0,0, return 0 to avoid FPs
  243. if (lat1f == 0.0 && long1f == 0.0) || (lat2f == 0.0 && long2f == 0.0) {
  244. log.Warningf("one of the coordinates is 0,0, returning 0")
  245. return 0.0, nil
  246. }
  247. first := haversine.Coord{Lat: lat1f, Lon: long1f}
  248. second := haversine.Coord{Lat: lat2f, Lon: long2f}
  249. _, km := haversine.Distance(first, second)
  250. return km, nil
  251. }
  252. // func QueryEscape(s string) string {
  253. func QueryEscape(params ...any) (any, error) {
  254. s := params[0].(string)
  255. return url.QueryEscape(s), nil
  256. }
  257. // func PathEscape(s string) string {
  258. func PathEscape(params ...any) (any, error) {
  259. s := params[0].(string)
  260. return url.PathEscape(s), nil
  261. }
  262. // func PathUnescape(s string) string {
  263. func PathUnescape(params ...any) (any, error) {
  264. s := params[0].(string)
  265. ret, err := url.PathUnescape(s)
  266. if err != nil {
  267. log.Debugf("unable to PathUnescape '%s': %+v", s, err)
  268. return s, nil
  269. }
  270. return ret, nil
  271. }
  272. // func QueryUnescape(s string) string {
  273. func QueryUnescape(params ...any) (any, error) {
  274. s := params[0].(string)
  275. ret, err := url.QueryUnescape(s)
  276. if err != nil {
  277. log.Debugf("unable to QueryUnescape '%s': %+v", s, err)
  278. return s, nil
  279. }
  280. return ret, nil
  281. }
  282. // func File(filename string) []string {
  283. func File(params ...any) (any, error) {
  284. filename := params[0].(string)
  285. if _, ok := dataFile[filename]; ok {
  286. return dataFile[filename], nil
  287. }
  288. log.Errorf("file '%s' (type:string) not found in expr library", filename)
  289. log.Errorf("expr library : %s", spew.Sdump(dataFile))
  290. return []string{}, nil
  291. }
  292. // func RegexpInFile(data string, filename string) bool {
  293. func RegexpInFile(params ...any) (any, error) {
  294. data := params[0].(string)
  295. filename := params[1].(string)
  296. var hash uint64
  297. hasCache := false
  298. matched := false
  299. if _, ok := dataFileRegexCache[filename]; ok {
  300. hasCache = true
  301. hash = xxhash.Sum64String(data)
  302. if val, err := dataFileRegexCache[filename].Get(hash); err == nil {
  303. return val.(bool), nil
  304. }
  305. }
  306. switch fflag.Re2RegexpInfileSupport.IsEnabled() {
  307. case true:
  308. if _, ok := dataFileRe2[filename]; ok {
  309. for _, re := range dataFileRe2[filename] {
  310. if re.MatchString(data) {
  311. matched = true
  312. break
  313. }
  314. }
  315. } else {
  316. log.Errorf("file '%s' (type:regexp) not found in expr library", filename)
  317. log.Errorf("expr library : %s", spew.Sdump(dataFileRe2))
  318. }
  319. case false:
  320. if _, ok := dataFileRegex[filename]; ok {
  321. for _, re := range dataFileRegex[filename] {
  322. if re.MatchString(data) {
  323. matched = true
  324. break
  325. }
  326. }
  327. } else {
  328. log.Errorf("file '%s' (type:regexp) not found in expr library", filename)
  329. log.Errorf("expr library : %s", spew.Sdump(dataFileRegex))
  330. }
  331. }
  332. if hasCache {
  333. dataFileRegexCache[filename].Set(hash, matched)
  334. }
  335. return matched, nil
  336. }
  337. // func IpInRange(ip string, ipRange string) bool {
  338. func IpInRange(params ...any) (any, error) {
  339. var err error
  340. var ipParsed net.IP
  341. var ipRangeParsed *net.IPNet
  342. ip := params[0].(string)
  343. ipRange := params[1].(string)
  344. ipParsed = net.ParseIP(ip)
  345. if ipParsed == nil {
  346. log.Debugf("'%s' is not a valid IP", ip)
  347. return false, nil
  348. }
  349. if _, ipRangeParsed, err = net.ParseCIDR(ipRange); err != nil {
  350. log.Debugf("'%s' is not a valid IP Range", ipRange)
  351. return false, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility
  352. }
  353. if ipRangeParsed.Contains(ipParsed) {
  354. return true, nil
  355. }
  356. return false, nil
  357. }
  358. // func IsIPV6(ip string) bool {
  359. func IsIPV6(params ...any) (any, error) {
  360. ip := params[0].(string)
  361. ipParsed := net.ParseIP(ip)
  362. if ipParsed == nil {
  363. log.Debugf("'%s' is not a valid IP", ip)
  364. return false, nil
  365. }
  366. // If it's a valid IP and can't be converted to IPv4 then it is an IPv6
  367. return ipParsed.To4() == nil, nil
  368. }
  369. // func IsIPV4(ip string) bool {
  370. func IsIPV4(params ...any) (any, error) {
  371. ip := params[0].(string)
  372. ipParsed := net.ParseIP(ip)
  373. if ipParsed == nil {
  374. log.Debugf("'%s' is not a valid IP", ip)
  375. return false, nil
  376. }
  377. return ipParsed.To4() != nil, nil
  378. }
  379. // func IsIP(ip string) bool {
  380. func IsIP(params ...any) (any, error) {
  381. ip := params[0].(string)
  382. ipParsed := net.ParseIP(ip)
  383. if ipParsed == nil {
  384. log.Debugf("'%s' is not a valid IP", ip)
  385. return false, nil
  386. }
  387. return true, nil
  388. }
  389. // func IpToRange(ip string, cidr string) string {
  390. func IpToRange(params ...any) (any, error) {
  391. ip := params[0].(string)
  392. cidr := params[1].(string)
  393. cidr = strings.TrimPrefix(cidr, "/")
  394. mask, err := strconv.Atoi(cidr)
  395. if err != nil {
  396. log.Errorf("bad cidr '%s': %s", cidr, err)
  397. return "", nil
  398. }
  399. ipAddr := net.ParseIP(ip)
  400. if ipAddr == nil {
  401. log.Errorf("can't parse IP address '%s'", ip)
  402. return "", nil
  403. }
  404. ipRange := iplib.NewNet(ipAddr, mask)
  405. if ipRange.IP() == nil {
  406. log.Errorf("can't get cidr '%s' of '%s'", cidr, ip)
  407. return "", nil
  408. }
  409. return ipRange.String(), nil
  410. }
  411. // func TimeNow() string {
  412. func TimeNow(params ...any) (any, error) {
  413. return time.Now().UTC().Format(time.RFC3339), nil
  414. }
  415. // func ParseUri(uri string) map[string][]string {
  416. func ParseUri(params ...any) (any, error) {
  417. uri := params[0].(string)
  418. ret := make(map[string][]string)
  419. u, err := url.Parse(uri)
  420. if err != nil {
  421. log.Errorf("Could not parse URI: %s", err)
  422. return ret, nil
  423. }
  424. parsed, err := url.ParseQuery(u.RawQuery)
  425. if err != nil {
  426. log.Errorf("Could not parse query uri : %s", err)
  427. return ret, nil
  428. }
  429. for k, v := range parsed {
  430. ret[k] = v
  431. }
  432. return ret, nil
  433. }
  434. // func KeyExists(key string, dict map[string]interface{}) bool {
  435. func KeyExists(params ...any) (any, error) {
  436. key := params[0].(string)
  437. dict := params[1].(map[string]interface{})
  438. _, ok := dict[key]
  439. return ok, nil
  440. }
  441. // func GetDecisionsCount(value string) int {
  442. func GetDecisionsCount(params ...any) (any, error) {
  443. value := params[0].(string)
  444. if dbClient == nil {
  445. log.Error("No database config to call GetDecisionsCount()")
  446. return 0, nil
  447. }
  448. count, err := dbClient.CountDecisionsByValue(value)
  449. if err != nil {
  450. log.Errorf("Failed to get decisions count from value '%s'", value)
  451. return 0, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility
  452. }
  453. return count, nil
  454. }
  455. // func GetDecisionsSinceCount(value string, since string) int {
  456. func GetDecisionsSinceCount(params ...any) (any, error) {
  457. value := params[0].(string)
  458. since := params[1].(string)
  459. if dbClient == nil {
  460. log.Error("No database config to call GetDecisionsCount()")
  461. return 0, nil
  462. }
  463. sinceDuration, err := time.ParseDuration(since)
  464. if err != nil {
  465. log.Errorf("Failed to parse since parameter '%s' : %s", since, err)
  466. return 0, nil
  467. }
  468. sinceTime := time.Now().UTC().Add(-sinceDuration)
  469. count, err := dbClient.CountDecisionsSinceByValue(value, sinceTime)
  470. if err != nil {
  471. log.Errorf("Failed to get decisions count from value '%s'", value)
  472. return 0, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility
  473. }
  474. return count, nil
  475. }
  476. // func LookupHost(value string) []string {
  477. func LookupHost(params ...any) (any, error) {
  478. value := params[0].(string)
  479. addresses, err := net.LookupHost(value)
  480. if err != nil {
  481. log.Errorf("Failed to lookup host '%s' : %s", value, err)
  482. return []string{}, nil
  483. }
  484. return addresses, nil
  485. }
  486. // func ParseUnixTime(value string) (time.Time, error) {
  487. func ParseUnixTime(params ...any) (any, error) {
  488. value := params[0].(string)
  489. //Splitting string here as some unix timestamp may have milliseconds and break ParseInt
  490. i, err := strconv.ParseInt(strings.Split(value, ".")[0], 10, 64)
  491. if err != nil || i <= 0 {
  492. return time.Time{}, fmt.Errorf("unable to parse %s as unix timestamp", value)
  493. }
  494. return time.Unix(i, 0), nil
  495. }
  496. // func ParseUnix(value string) string {
  497. func ParseUnix(params ...any) (any, error) {
  498. value := params[0].(string)
  499. t, err := ParseUnixTime(value)
  500. if err != nil {
  501. log.Error(err)
  502. return "", nil
  503. }
  504. return t.(time.Time).Format(time.RFC3339), nil
  505. }
  506. // func ToString(value interface{}) string {
  507. func ToString(params ...any) (any, error) {
  508. value := params[0]
  509. s, ok := value.(string)
  510. if !ok {
  511. return "", nil
  512. }
  513. return s, nil
  514. }
  515. // func GetFromStash(cacheName string, key string) (string, error) {
  516. func GetFromStash(params ...any) (any, error) {
  517. cacheName := params[0].(string)
  518. key := params[1].(string)
  519. return cache.GetKey(cacheName, key)
  520. }
  521. // func SetInStash(cacheName string, key string, value string, expiration *time.Duration) any {
  522. func SetInStash(params ...any) (any, error) {
  523. cacheName := params[0].(string)
  524. key := params[1].(string)
  525. value := params[2].(string)
  526. expiration := params[3].(*time.Duration)
  527. return cache.SetKey(cacheName, key, value, expiration), nil
  528. }
  529. func Sprintf(params ...any) (any, error) {
  530. format := params[0].(string)
  531. return fmt.Sprintf(format, params[1:]...), nil
  532. }
  533. // func Match(pattern, name string) bool {
  534. func Match(params ...any) (any, error) {
  535. var matched bool
  536. pattern := params[0].(string)
  537. name := params[1].(string)
  538. if pattern == "" {
  539. return name == "", nil
  540. }
  541. if name == "" {
  542. if pattern == "*" || pattern == "" {
  543. return true, nil
  544. }
  545. return false, nil
  546. }
  547. if pattern[0] == '*' {
  548. for i := 0; i <= len(name); i++ {
  549. matched, _ := Match(pattern[1:], name[i:])
  550. if matched.(bool) {
  551. return matched, nil
  552. }
  553. }
  554. return matched, nil
  555. }
  556. if pattern[0] == '?' || pattern[0] == name[0] {
  557. return Match(pattern[1:], name[1:])
  558. }
  559. return matched, nil
  560. }
  561. func B64Decode(params ...any) (any, error) {
  562. encoded := params[0].(string)
  563. decoded, err := base64.StdEncoding.DecodeString(encoded)
  564. if err != nil {
  565. return "", err
  566. }
  567. return string(decoded), nil
  568. }
  569. func ParseKV(params ...any) (any, error) {
  570. blob := params[0].(string)
  571. target := params[1].(map[string]interface{})
  572. prefix := params[2].(string)
  573. matches := keyValuePattern.FindAllStringSubmatch(blob, -1)
  574. if matches == nil {
  575. log.Errorf("could not find any key/value pair in line")
  576. return nil, fmt.Errorf("invalid input format")
  577. }
  578. if _, ok := target[prefix]; !ok {
  579. target[prefix] = make(map[string]string)
  580. } else {
  581. _, ok := target[prefix].(map[string]string)
  582. if !ok {
  583. log.Errorf("ParseKV: target is not a map[string]string")
  584. return nil, fmt.Errorf("target is not a map[string]string")
  585. }
  586. }
  587. for _, match := range matches {
  588. key := ""
  589. value := ""
  590. for i, name := range keyValuePattern.SubexpNames() {
  591. if name == "key" {
  592. key = match[i]
  593. } else if name == "quoted_value" && match[i] != "" {
  594. value = match[i]
  595. } else if name == "value" && match[i] != "" {
  596. value = match[i]
  597. }
  598. }
  599. target[prefix].(map[string]string)[key] = value
  600. }
  601. log.Tracef("unmarshaled KV: %+v", target[prefix])
  602. return nil, nil
  603. }
  604. func Hostname(params ...any) (any, error) {
  605. hostname, err := os.Hostname()
  606. if err != nil {
  607. return "", err
  608. }
  609. return hostname, nil
  610. }