krb5conf.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. // Package config implements KRB5 client and service configuration as described at https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html
  2. package config
  3. import (
  4. "bufio"
  5. "encoding/hex"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net"
  11. "os"
  12. "os/user"
  13. "regexp"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "github.com/jcmturner/gofork/encoding/asn1"
  18. "github.com/jcmturner/gokrb5/v8/iana/etypeID"
  19. )
  20. // Config represents the KRB5 configuration.
  21. type Config struct {
  22. LibDefaults LibDefaults
  23. Realms []Realm
  24. DomainRealm DomainRealm
  25. //CaPaths
  26. //AppDefaults
  27. //Plugins
  28. }
  29. // WeakETypeList is a list of encryption types that have been deemed weak.
  30. const WeakETypeList = "des-cbc-crc des-cbc-md4 des-cbc-md5 des-cbc-raw des3-cbc-raw des-hmac-sha1 arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp des"
  31. // New creates a new config struct instance.
  32. func New() *Config {
  33. d := make(DomainRealm)
  34. return &Config{
  35. LibDefaults: newLibDefaults(),
  36. DomainRealm: d,
  37. }
  38. }
  39. // LibDefaults represents the [libdefaults] section of the configuration.
  40. type LibDefaults struct {
  41. AllowWeakCrypto bool //default false
  42. // ap_req_checksum_type int //unlikely to support this
  43. Canonicalize bool //default false
  44. CCacheType int //default is 4. unlikely to implement older
  45. Clockskew time.Duration //max allowed skew in seconds, default 300
  46. //Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory
  47. DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab
  48. DefaultKeytabName string //default /etc/krb5.keytab
  49. DefaultRealm string
  50. DefaultTGSEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
  51. DefaultTktEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
  52. DefaultTGSEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
  53. DefaultTktEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
  54. DNSCanonicalizeHostname bool //default true
  55. DNSLookupKDC bool //default false
  56. DNSLookupRealm bool
  57. ExtraAddresses []net.IP //Not implementing yet
  58. Forwardable bool //default false
  59. IgnoreAcceptorHostname bool //default false
  60. K5LoginAuthoritative bool //default false
  61. K5LoginDirectory string //default user's home directory. Must be owned by the user or root
  62. KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK)
  63. KDCTimeSync int //default 1
  64. //kdc_req_checksum_type int //unlikely to implement as for very old KDCs
  65. NoAddresses bool //default true
  66. PermittedEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
  67. PermittedEnctypeIDs []int32
  68. //plugin_base_dir string //not supporting plugins
  69. PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported
  70. Proxiable bool //default false
  71. RDNS bool //default true
  72. RealmTryDomains int //default -1
  73. RenewLifetime time.Duration //default 0
  74. SafeChecksumType int //default 8
  75. TicketLifetime time.Duration //default 1 day
  76. UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700.
  77. VerifyAPReqNofail bool //default false
  78. }
  79. // Create a new LibDefaults struct.
  80. func newLibDefaults() LibDefaults {
  81. uid := "0"
  82. var hdir string
  83. usr, _ := user.Current()
  84. if usr != nil {
  85. uid = usr.Uid
  86. hdir = usr.HomeDir
  87. }
  88. opts := asn1.BitString{}
  89. opts.Bytes, _ = hex.DecodeString("00000010")
  90. opts.BitLength = len(opts.Bytes) * 8
  91. l := LibDefaults{
  92. CCacheType: 4,
  93. Clockskew: time.Duration(300) * time.Second,
  94. DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid),
  95. DefaultKeytabName: "/etc/krb5.keytab",
  96. DefaultTGSEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
  97. DefaultTktEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
  98. DNSCanonicalizeHostname: true,
  99. K5LoginDirectory: hdir,
  100. KDCDefaultOptions: opts,
  101. KDCTimeSync: 1,
  102. NoAddresses: true,
  103. PermittedEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
  104. RDNS: true,
  105. RealmTryDomains: -1,
  106. SafeChecksumType: 8,
  107. TicketLifetime: time.Duration(24) * time.Hour,
  108. UDPPreferenceLimit: 1465,
  109. PreferredPreauthTypes: []int{17, 16, 15, 14},
  110. }
  111. l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
  112. l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
  113. l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
  114. return l
  115. }
  116. // Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct.
  117. func (l *LibDefaults) parseLines(lines []string) error {
  118. for _, line := range lines {
  119. //Remove comments after the values
  120. if idx := strings.IndexAny(line, "#;"); idx != -1 {
  121. line = line[:idx]
  122. }
  123. line = strings.TrimSpace(line)
  124. if line == "" {
  125. continue
  126. }
  127. if !strings.Contains(line, "=") {
  128. return InvalidErrorf("libdefaults section line (%s)", line)
  129. }
  130. p := strings.Split(line, "=")
  131. key := strings.TrimSpace(strings.ToLower(p[0]))
  132. switch key {
  133. case "allow_weak_crypto":
  134. v, err := parseBoolean(p[1])
  135. if err != nil {
  136. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  137. }
  138. l.AllowWeakCrypto = v
  139. case "canonicalize":
  140. v, err := parseBoolean(p[1])
  141. if err != nil {
  142. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  143. }
  144. l.Canonicalize = v
  145. case "ccache_type":
  146. p[1] = strings.TrimSpace(p[1])
  147. v, err := strconv.ParseUint(p[1], 10, 32)
  148. if err != nil || v < 0 || v > 4 {
  149. return InvalidErrorf("libdefaults section line (%s)", line)
  150. }
  151. l.CCacheType = int(v)
  152. case "clockskew":
  153. d, err := parseDuration(p[1])
  154. if err != nil {
  155. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  156. }
  157. l.Clockskew = d
  158. case "default_client_keytab_name":
  159. l.DefaultClientKeytabName = strings.TrimSpace(p[1])
  160. case "default_keytab_name":
  161. l.DefaultKeytabName = strings.TrimSpace(p[1])
  162. case "default_realm":
  163. l.DefaultRealm = strings.TrimSpace(p[1])
  164. case "default_tgs_enctypes":
  165. l.DefaultTGSEnctypes = strings.Fields(p[1])
  166. case "default_tkt_enctypes":
  167. l.DefaultTktEnctypes = strings.Fields(p[1])
  168. case "dns_canonicalize_hostname":
  169. v, err := parseBoolean(p[1])
  170. if err != nil {
  171. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  172. }
  173. l.DNSCanonicalizeHostname = v
  174. case "dns_lookup_kdc":
  175. v, err := parseBoolean(p[1])
  176. if err != nil {
  177. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  178. }
  179. l.DNSLookupKDC = v
  180. case "dns_lookup_realm":
  181. v, err := parseBoolean(p[1])
  182. if err != nil {
  183. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  184. }
  185. l.DNSLookupRealm = v
  186. case "extra_addresses":
  187. ipStr := strings.TrimSpace(p[1])
  188. for _, ip := range strings.Split(ipStr, ",") {
  189. if eip := net.ParseIP(ip); eip != nil {
  190. l.ExtraAddresses = append(l.ExtraAddresses, eip)
  191. }
  192. }
  193. case "forwardable":
  194. v, err := parseBoolean(p[1])
  195. if err != nil {
  196. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  197. }
  198. l.Forwardable = v
  199. case "ignore_acceptor_hostname":
  200. v, err := parseBoolean(p[1])
  201. if err != nil {
  202. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  203. }
  204. l.IgnoreAcceptorHostname = v
  205. case "k5login_authoritative":
  206. v, err := parseBoolean(p[1])
  207. if err != nil {
  208. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  209. }
  210. l.K5LoginAuthoritative = v
  211. case "k5login_directory":
  212. l.K5LoginDirectory = strings.TrimSpace(p[1])
  213. case "kdc_default_options":
  214. v := strings.TrimSpace(p[1])
  215. v = strings.Replace(v, "0x", "", -1)
  216. b, err := hex.DecodeString(v)
  217. if err != nil {
  218. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  219. }
  220. l.KDCDefaultOptions.Bytes = b
  221. l.KDCDefaultOptions.BitLength = len(b) * 8
  222. case "kdc_timesync":
  223. p[1] = strings.TrimSpace(p[1])
  224. v, err := strconv.ParseInt(p[1], 10, 32)
  225. if err != nil || v < 0 {
  226. return InvalidErrorf("libdefaults section line (%s)", line)
  227. }
  228. l.KDCTimeSync = int(v)
  229. case "noaddresses":
  230. v, err := parseBoolean(p[1])
  231. if err != nil {
  232. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  233. }
  234. l.NoAddresses = v
  235. case "permitted_enctypes":
  236. l.PermittedEnctypes = strings.Fields(p[1])
  237. case "preferred_preauth_types":
  238. p[1] = strings.TrimSpace(p[1])
  239. t := strings.Split(p[1], ",")
  240. var v []int
  241. for _, s := range t {
  242. i, err := strconv.ParseInt(s, 10, 32)
  243. if err != nil {
  244. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  245. }
  246. v = append(v, int(i))
  247. }
  248. l.PreferredPreauthTypes = v
  249. case "proxiable":
  250. v, err := parseBoolean(p[1])
  251. if err != nil {
  252. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  253. }
  254. l.Proxiable = v
  255. case "rdns":
  256. v, err := parseBoolean(p[1])
  257. if err != nil {
  258. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  259. }
  260. l.RDNS = v
  261. case "realm_try_domains":
  262. p[1] = strings.TrimSpace(p[1])
  263. v, err := strconv.ParseInt(p[1], 10, 32)
  264. if err != nil || v < -1 {
  265. return InvalidErrorf("libdefaults section line (%s)", line)
  266. }
  267. l.RealmTryDomains = int(v)
  268. case "renew_lifetime":
  269. d, err := parseDuration(p[1])
  270. if err != nil {
  271. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  272. }
  273. l.RenewLifetime = d
  274. case "safe_checksum_type":
  275. p[1] = strings.TrimSpace(p[1])
  276. v, err := strconv.ParseInt(p[1], 10, 32)
  277. if err != nil || v < 0 {
  278. return InvalidErrorf("libdefaults section line (%s)", line)
  279. }
  280. l.SafeChecksumType = int(v)
  281. case "ticket_lifetime":
  282. d, err := parseDuration(p[1])
  283. if err != nil {
  284. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  285. }
  286. l.TicketLifetime = d
  287. case "udp_preference_limit":
  288. p[1] = strings.TrimSpace(p[1])
  289. v, err := strconv.ParseUint(p[1], 10, 32)
  290. if err != nil || v > 32700 {
  291. return InvalidErrorf("libdefaults section line (%s)", line)
  292. }
  293. l.UDPPreferenceLimit = int(v)
  294. case "verify_ap_req_nofail":
  295. v, err := parseBoolean(p[1])
  296. if err != nil {
  297. return InvalidErrorf("libdefaults section line (%s): %v", line, err)
  298. }
  299. l.VerifyAPReqNofail = v
  300. }
  301. }
  302. l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
  303. l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
  304. l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
  305. return nil
  306. }
  307. // Realm represents an entry in the [realms] section of the configuration.
  308. type Realm struct {
  309. Realm string
  310. AdminServer []string
  311. //auth_to_local //Not implementing for now
  312. //auth_to_local_names //Not implementing for now
  313. DefaultDomain string
  314. KDC []string
  315. KPasswdServer []string //default admin_server:464
  316. MasterKDC []string
  317. }
  318. // Parse the lines of a [realms] entry into the Realm struct.
  319. func (r *Realm) parseLines(name string, lines []string) (err error) {
  320. r.Realm = name
  321. var adminServerFinal bool
  322. var KDCFinal bool
  323. var kpasswdServerFinal bool
  324. var masterKDCFinal bool
  325. var ignore bool
  326. var c int // counts the depth of blocks within brackets { }
  327. for _, line := range lines {
  328. if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") {
  329. continue
  330. }
  331. //Remove comments after the values
  332. if idx := strings.IndexAny(line, "#;"); idx != -1 {
  333. line = line[:idx]
  334. }
  335. line = strings.TrimSpace(line)
  336. if line == "" {
  337. continue
  338. }
  339. if !strings.Contains(line, "=") && !strings.Contains(line, "}") {
  340. return InvalidErrorf("realms section line (%s)", line)
  341. }
  342. if strings.Contains(line, "v4_") {
  343. ignore = true
  344. err = UnsupportedDirective{"v4 configurations are not supported"}
  345. }
  346. if strings.Contains(line, "{") {
  347. c++
  348. if ignore {
  349. continue
  350. }
  351. }
  352. if strings.Contains(line, "}") {
  353. c--
  354. if c < 0 {
  355. return InvalidErrorf("unpaired curly brackets")
  356. }
  357. if ignore {
  358. if c < 1 {
  359. c = 0
  360. ignore = false
  361. }
  362. continue
  363. }
  364. }
  365. p := strings.Split(line, "=")
  366. key := strings.TrimSpace(strings.ToLower(p[0]))
  367. v := strings.TrimSpace(p[1])
  368. switch key {
  369. case "admin_server":
  370. appendUntilFinal(&r.AdminServer, v, &adminServerFinal)
  371. case "default_domain":
  372. r.DefaultDomain = v
  373. case "kdc":
  374. if !strings.Contains(v, ":") {
  375. // No port number specified default to 88
  376. if strings.HasSuffix(v, `*`) {
  377. v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*"
  378. } else {
  379. v = strings.TrimSpace(v) + ":88"
  380. }
  381. }
  382. appendUntilFinal(&r.KDC, v, &KDCFinal)
  383. case "kpasswd_server":
  384. appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal)
  385. case "master_kdc":
  386. appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal)
  387. }
  388. }
  389. //default for Kpasswd_server = admin_server:464
  390. if len(r.KPasswdServer) < 1 {
  391. for _, a := range r.AdminServer {
  392. s := strings.Split(a, ":")
  393. r.KPasswdServer = append(r.KPasswdServer, s[0]+":464")
  394. }
  395. }
  396. return
  397. }
  398. // Parse the lines of the [realms] section of the configuration into an slice of Realm structs.
  399. func parseRealms(lines []string) (realms []Realm, err error) {
  400. var name string
  401. var start int
  402. var c int
  403. for i, l := range lines {
  404. //Remove comments after the values
  405. if idx := strings.IndexAny(l, "#;"); idx != -1 {
  406. l = l[:idx]
  407. }
  408. l = strings.TrimSpace(l)
  409. if l == "" {
  410. continue
  411. }
  412. //if strings.Contains(l, "v4_") {
  413. // return nil, errors.New("v4 configurations are not supported in Realms section")
  414. //}
  415. if strings.Contains(l, "{") {
  416. c++
  417. if !strings.Contains(l, "=") {
  418. return nil, fmt.Errorf("realm configuration line invalid: %s", l)
  419. }
  420. if c == 1 {
  421. start = i
  422. p := strings.Split(l, "=")
  423. name = strings.TrimSpace(p[0])
  424. }
  425. }
  426. if strings.Contains(l, "}") {
  427. if c < 1 {
  428. // but not started a block!!!
  429. return nil, errors.New("invalid Realms section in configuration")
  430. }
  431. c--
  432. if c == 0 {
  433. var r Realm
  434. e := r.parseLines(name, lines[start+1:i])
  435. if e != nil {
  436. if _, ok := e.(UnsupportedDirective); !ok {
  437. err = e
  438. return
  439. }
  440. err = e
  441. }
  442. realms = append(realms, r)
  443. }
  444. }
  445. }
  446. return
  447. }
  448. // DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration.
  449. type DomainRealm map[string]string
  450. // Parse the lines of the [domain_realm] section of the configuration and add to the mapping.
  451. func (d *DomainRealm) parseLines(lines []string) error {
  452. for _, line := range lines {
  453. //Remove comments after the values
  454. if idx := strings.IndexAny(line, "#;"); idx != -1 {
  455. line = line[:idx]
  456. }
  457. if strings.TrimSpace(line) == "" {
  458. continue
  459. }
  460. if !strings.Contains(line, "=") {
  461. return InvalidErrorf("realm line (%s)", line)
  462. }
  463. p := strings.Split(line, "=")
  464. domain := strings.TrimSpace(strings.ToLower(p[0]))
  465. realm := strings.TrimSpace(p[1])
  466. d.addMapping(domain, realm)
  467. }
  468. return nil
  469. }
  470. // Add a domain to realm mapping.
  471. func (d *DomainRealm) addMapping(domain, realm string) {
  472. (*d)[domain] = realm
  473. }
  474. // Delete a domain to realm mapping.
  475. func (d *DomainRealm) deleteMapping(domain, realm string) {
  476. delete(*d, domain)
  477. }
  478. // ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping.
  479. // The most specific mapping is returned.
  480. func (c *Config) ResolveRealm(domainName string) string {
  481. domainName = strings.TrimSuffix(domainName, ".")
  482. // Try to match the entire hostname first
  483. if r, ok := c.DomainRealm[domainName]; ok {
  484. return r
  485. }
  486. // Try to match all DNS domain parts
  487. periods := strings.Count(domainName, ".") + 1
  488. for i := 2; i <= periods; i++ {
  489. z := strings.SplitN(domainName, ".", i)
  490. if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok {
  491. return r
  492. }
  493. }
  494. return ""
  495. }
  496. // Load the KRB5 configuration from the specified file path.
  497. func Load(cfgPath string) (*Config, error) {
  498. fh, err := os.Open(cfgPath)
  499. if err != nil {
  500. return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error())
  501. }
  502. defer fh.Close()
  503. scanner := bufio.NewScanner(fh)
  504. return NewFromScanner(scanner)
  505. }
  506. // NewFromString creates a new Config struct from a string.
  507. func NewFromString(s string) (*Config, error) {
  508. reader := strings.NewReader(s)
  509. return NewFromReader(reader)
  510. }
  511. // NewFromReader creates a new Config struct from an io.Reader.
  512. func NewFromReader(r io.Reader) (*Config, error) {
  513. scanner := bufio.NewScanner(r)
  514. return NewFromScanner(scanner)
  515. }
  516. // NewFromScanner creates a new Config struct from a bufio.Scanner.
  517. func NewFromScanner(scanner *bufio.Scanner) (*Config, error) {
  518. c := New()
  519. var e error
  520. sections := make(map[int]string)
  521. var sectionLineNum []int
  522. var lines []string
  523. for scanner.Scan() {
  524. // Skip comments and blank lines
  525. if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched {
  526. continue
  527. }
  528. if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched {
  529. sections[len(lines)] = "libdefaults"
  530. sectionLineNum = append(sectionLineNum, len(lines))
  531. continue
  532. }
  533. if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched {
  534. sections[len(lines)] = "realms"
  535. sectionLineNum = append(sectionLineNum, len(lines))
  536. continue
  537. }
  538. if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched {
  539. sections[len(lines)] = "domain_realm"
  540. sectionLineNum = append(sectionLineNum, len(lines))
  541. continue
  542. }
  543. if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched {
  544. sections[len(lines)] = "unknown_section"
  545. sectionLineNum = append(sectionLineNum, len(lines))
  546. continue
  547. }
  548. lines = append(lines, scanner.Text())
  549. }
  550. for i, start := range sectionLineNum {
  551. var end int
  552. if i+1 >= len(sectionLineNum) {
  553. end = len(lines)
  554. } else {
  555. end = sectionLineNum[i+1]
  556. }
  557. switch section := sections[start]; section {
  558. case "libdefaults":
  559. err := c.LibDefaults.parseLines(lines[start:end])
  560. if err != nil {
  561. if _, ok := err.(UnsupportedDirective); !ok {
  562. return nil, fmt.Errorf("error processing libdefaults section: %v", err)
  563. }
  564. e = err
  565. }
  566. case "realms":
  567. realms, err := parseRealms(lines[start:end])
  568. if err != nil {
  569. if _, ok := err.(UnsupportedDirective); !ok {
  570. return nil, fmt.Errorf("error processing realms section: %v", err)
  571. }
  572. e = err
  573. }
  574. c.Realms = realms
  575. case "domain_realm":
  576. err := c.DomainRealm.parseLines(lines[start:end])
  577. if err != nil {
  578. if _, ok := err.(UnsupportedDirective); !ok {
  579. return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
  580. }
  581. e = err
  582. }
  583. }
  584. }
  585. return c, e
  586. }
  587. // Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes.
  588. func parseETypes(s []string, w bool) []int32 {
  589. var eti []int32
  590. for _, et := range s {
  591. if !w {
  592. var weak bool
  593. for _, wet := range strings.Fields(WeakETypeList) {
  594. if et == wet {
  595. weak = true
  596. break
  597. }
  598. }
  599. if weak {
  600. continue
  601. }
  602. }
  603. i := etypeID.EtypeSupported(et)
  604. if i != 0 {
  605. eti = append(eti, i)
  606. }
  607. }
  608. return eti
  609. }
  610. // Parse a time duration string in the configuration to a golang time.Duration.
  611. func parseDuration(s string) (time.Duration, error) {
  612. s = strings.Replace(strings.TrimSpace(s), " ", "", -1)
  613. // handle Nd[NmNs]
  614. if strings.Contains(s, "d") {
  615. ds := strings.SplitN(s, "d", 2)
  616. dn, err := strconv.ParseUint(ds[0], 10, 32)
  617. if err != nil {
  618. return time.Duration(0), errors.New("invalid time duration")
  619. }
  620. d := time.Duration(dn*24) * time.Hour
  621. if ds[1] != "" {
  622. dp, err := time.ParseDuration(ds[1])
  623. if err != nil {
  624. return time.Duration(0), errors.New("invalid time duration")
  625. }
  626. d = d + dp
  627. }
  628. return d, nil
  629. }
  630. // handle Nm[Ns]
  631. d, err := time.ParseDuration(s)
  632. if err == nil {
  633. return d, nil
  634. }
  635. // handle N
  636. v, err := strconv.ParseUint(s, 10, 32)
  637. if err == nil && v > 0 {
  638. return time.Duration(v) * time.Second, nil
  639. }
  640. // handle h:m[:s]
  641. if strings.Contains(s, ":") {
  642. t := strings.Split(s, ":")
  643. if 2 > len(t) || len(t) > 3 {
  644. return time.Duration(0), errors.New("invalid time duration value")
  645. }
  646. var i []int
  647. for _, n := range t {
  648. j, err := strconv.ParseInt(n, 10, 16)
  649. if err != nil {
  650. return time.Duration(0), errors.New("invalid time duration value")
  651. }
  652. i = append(i, int(j))
  653. }
  654. d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute
  655. if len(i) == 3 {
  656. d = d + time.Duration(i[2])*time.Second
  657. }
  658. return d, nil
  659. }
  660. return time.Duration(0), errors.New("invalid time duration value")
  661. }
  662. // Parse possible boolean values to golang bool.
  663. func parseBoolean(s string) (bool, error) {
  664. s = strings.TrimSpace(s)
  665. v, err := strconv.ParseBool(s)
  666. if err == nil {
  667. return v, nil
  668. }
  669. switch strings.ToLower(s) {
  670. case "yes":
  671. return true, nil
  672. case "y":
  673. return true, nil
  674. case "no":
  675. return false, nil
  676. case "n":
  677. return false, nil
  678. }
  679. return false, errors.New("invalid boolean value")
  680. }
  681. // Parse array of strings but stop if an asterisk is placed at the end of a line.
  682. func appendUntilFinal(s *[]string, value string, final *bool) {
  683. if *final {
  684. return
  685. }
  686. if last := len(value) - 1; last >= 0 && value[last] == '*' {
  687. *final = true
  688. value = value[:len(value)-1]
  689. }
  690. *s = append(*s, value)
  691. }
  692. // JSON return details of the config in a JSON format.
  693. func (c *Config) JSON() (string, error) {
  694. b, err := json.MarshalIndent(c, "", " ")
  695. if err != nil {
  696. return "", err
  697. }
  698. return string(b), nil
  699. }