server_search_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. package ldap
  2. import (
  3. "os/exec"
  4. "strings"
  5. "testing"
  6. "time"
  7. )
  8. //
  9. func TestSearchSimpleOK(t *testing.T) {
  10. quit := make(chan bool)
  11. done := make(chan bool)
  12. go func() {
  13. s := NewServer()
  14. s.QuitChannel(quit)
  15. s.SearchFunc("", searchSimple{})
  16. s.BindFunc("", bindSimple{})
  17. if err := s.ListenAndServe(listenString); err != nil {
  18. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  19. }
  20. }()
  21. serverBaseDN := "o=testers,c=test"
  22. go func() {
  23. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  24. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test")
  25. out, _ := cmd.CombinedOutput()
  26. if !strings.Contains(string(out), "dn: cn=ned,o=testers,c=test") {
  27. t.Errorf("ldapsearch failed: %v", string(out))
  28. }
  29. if !strings.Contains(string(out), "uidNumber: 5000") {
  30. t.Errorf("ldapsearch failed: %v", string(out))
  31. }
  32. if !strings.Contains(string(out), "result: 0 Success") {
  33. t.Errorf("ldapsearch failed: %v", string(out))
  34. }
  35. if !strings.Contains(string(out), "numResponses: 4") {
  36. t.Errorf("ldapsearch failed: %v", string(out))
  37. }
  38. done <- true
  39. }()
  40. select {
  41. case <-done:
  42. case <-time.After(timeout):
  43. t.Errorf("ldapsearch command timed out")
  44. }
  45. quit <- true
  46. }
  47. func TestSearchSizelimit(t *testing.T) {
  48. quit := make(chan bool)
  49. done := make(chan bool)
  50. go func() {
  51. s := NewServer()
  52. s.EnforceLDAP = true
  53. s.QuitChannel(quit)
  54. s.SearchFunc("", searchSimple{})
  55. s.BindFunc("", bindSimple{})
  56. if err := s.ListenAndServe(listenString); err != nil {
  57. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  58. }
  59. }()
  60. go func() {
  61. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  62. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test") // no limit for this test
  63. out, _ := cmd.CombinedOutput()
  64. if !strings.Contains(string(out), "result: 0 Success") {
  65. t.Errorf("ldapsearch failed: %v", string(out))
  66. }
  67. if !strings.Contains(string(out), "numEntries: 3") {
  68. t.Errorf("ldapsearch sizelimit unlimited failed - not enough entries: %v", string(out))
  69. }
  70. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  71. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-z", "9") // effectively no limit for this test
  72. out, _ = cmd.CombinedOutput()
  73. if !strings.Contains(string(out), "result: 0 Success") {
  74. t.Errorf("ldapsearch failed: %v", string(out))
  75. }
  76. if !strings.Contains(string(out), "numEntries: 3") {
  77. t.Errorf("ldapsearch sizelimit 9 failed - not enough entries: %v", string(out))
  78. }
  79. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  80. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-z", "2")
  81. out, _ = cmd.CombinedOutput()
  82. if !strings.Contains(string(out), "result: 0 Success") {
  83. t.Errorf("ldapsearch failed: %v", string(out))
  84. }
  85. if !strings.Contains(string(out), "numEntries: 2") {
  86. t.Errorf("ldapsearch sizelimit 2 failed - too many entries: %v", string(out))
  87. }
  88. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  89. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-z", "1")
  90. out, _ = cmd.CombinedOutput()
  91. if !strings.Contains(string(out), "result: 0 Success") {
  92. t.Errorf("ldapsearch failed: %v", string(out))
  93. }
  94. if !strings.Contains(string(out), "numEntries: 1") {
  95. t.Errorf("ldapsearch sizelimit 1 failed - too many entries: %v", string(out))
  96. }
  97. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  98. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-z", "0")
  99. out, _ = cmd.CombinedOutput()
  100. if !strings.Contains(string(out), "result: 0 Success") {
  101. t.Errorf("ldapsearch failed: %v", string(out))
  102. }
  103. if !strings.Contains(string(out), "numEntries: 3") {
  104. t.Errorf("ldapsearch sizelimit 0 failed - wrong number of entries: %v", string(out))
  105. }
  106. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  107. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-z", "1", "(uid=trent)")
  108. out, _ = cmd.CombinedOutput()
  109. if !strings.Contains(string(out), "result: 0 Success") {
  110. t.Errorf("ldapsearch failed: %v", string(out))
  111. }
  112. if !strings.Contains(string(out), "numEntries: 1") {
  113. t.Errorf("ldapsearch sizelimit 1 with filter failed - wrong number of entries: %v", string(out))
  114. }
  115. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  116. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-z", "0", "(uid=trent)")
  117. out, _ = cmd.CombinedOutput()
  118. if !strings.Contains(string(out), "result: 0 Success") {
  119. t.Errorf("ldapsearch failed: %v", string(out))
  120. }
  121. if !strings.Contains(string(out), "numEntries: 1") {
  122. t.Errorf("ldapsearch sizelimit 0 with filter failed - wrong number of entries: %v", string(out))
  123. }
  124. done <- true
  125. }()
  126. select {
  127. case <-done:
  128. case <-time.After(timeout):
  129. t.Errorf("ldapsearch command timed out")
  130. }
  131. quit <- true
  132. }
  133. /////////////////////////
  134. func TestBindSearchMulti(t *testing.T) {
  135. quit := make(chan bool)
  136. done := make(chan bool)
  137. go func() {
  138. s := NewServer()
  139. s.QuitChannel(quit)
  140. s.BindFunc("", bindSimple{})
  141. s.BindFunc("c=testz", bindSimple2{})
  142. s.SearchFunc("", searchSimple{})
  143. s.SearchFunc("c=testz", searchSimple2{})
  144. if err := s.ListenAndServe(listenString); err != nil {
  145. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  146. }
  147. }()
  148. go func() {
  149. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=test",
  150. "-D", "cn=testy,o=testers,c=test", "-w", "iLike2test", "cn=ned")
  151. out, _ := cmd.CombinedOutput()
  152. if !strings.Contains(string(out), "result: 0 Success") {
  153. t.Errorf("error routing default bind/search functions: %v", string(out))
  154. }
  155. if !strings.Contains(string(out), "dn: cn=ned,o=testers,c=test") {
  156. t.Errorf("search default routing failed: %v", string(out))
  157. }
  158. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=testz",
  159. "-D", "cn=testy,o=testers,c=testz", "-w", "ZLike2test", "cn=hamburger")
  160. out, _ = cmd.CombinedOutput()
  161. if !strings.Contains(string(out), "result: 0 Success") {
  162. t.Errorf("error routing custom bind/search functions: %v", string(out))
  163. }
  164. if !strings.Contains(string(out), "dn: cn=hamburger,o=testers,c=testz") {
  165. t.Errorf("search custom routing failed: %v", string(out))
  166. }
  167. done <- true
  168. }()
  169. select {
  170. case <-done:
  171. case <-time.After(timeout):
  172. t.Errorf("ldapsearch command timed out")
  173. }
  174. quit <- true
  175. }
  176. /////////////////////////
  177. func TestSearchPanic(t *testing.T) {
  178. quit := make(chan bool)
  179. done := make(chan bool)
  180. go func() {
  181. s := NewServer()
  182. s.QuitChannel(quit)
  183. s.SearchFunc("", searchPanic{})
  184. s.BindFunc("", bindAnonOK{})
  185. if err := s.ListenAndServe(listenString); err != nil {
  186. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  187. }
  188. }()
  189. go func() {
  190. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=test")
  191. out, _ := cmd.CombinedOutput()
  192. if !strings.Contains(string(out), "result: 1 Operations error") {
  193. t.Errorf("ldapsearch should have returned operations error due to panic: %v", string(out))
  194. }
  195. done <- true
  196. }()
  197. select {
  198. case <-done:
  199. case <-time.After(timeout):
  200. t.Errorf("ldapsearch command timed out")
  201. }
  202. quit <- true
  203. }
  204. /////////////////////////
  205. type compileSearchFilterTest struct {
  206. name string
  207. filterStr string
  208. numResponses string
  209. }
  210. var searchFilterTestFilters = []compileSearchFilterTest{
  211. compileSearchFilterTest{name: "equalityOk", filterStr: "(uid=ned)", numResponses: "2"},
  212. compileSearchFilterTest{name: "equalityNo", filterStr: "(uid=foo)", numResponses: "1"},
  213. compileSearchFilterTest{name: "equalityOk", filterStr: "(objectclass=posixaccount)", numResponses: "4"},
  214. compileSearchFilterTest{name: "presentEmptyOk", filterStr: "", numResponses: "4"},
  215. compileSearchFilterTest{name: "presentOk", filterStr: "(objectclass=*)", numResponses: "4"},
  216. compileSearchFilterTest{name: "presentOk", filterStr: "(description=*)", numResponses: "3"},
  217. compileSearchFilterTest{name: "presentNo", filterStr: "(foo=*)", numResponses: "1"},
  218. compileSearchFilterTest{name: "andOk", filterStr: "(&(uid=ned)(objectclass=posixaccount))", numResponses: "2"},
  219. compileSearchFilterTest{name: "andNo", filterStr: "(&(uid=ned)(objectclass=posixgroup))", numResponses: "1"},
  220. compileSearchFilterTest{name: "andNo", filterStr: "(&(uid=ned)(uid=trent))", numResponses: "1"},
  221. compileSearchFilterTest{name: "orOk", filterStr: "(|(uid=ned)(uid=trent))", numResponses: "3"},
  222. compileSearchFilterTest{name: "orOk", filterStr: "(|(uid=ned)(objectclass=posixaccount))", numResponses: "4"},
  223. compileSearchFilterTest{name: "orNo", filterStr: "(|(uid=foo)(objectclass=foo))", numResponses: "1"},
  224. compileSearchFilterTest{name: "andOrOk", filterStr: "(&(|(uid=ned)(uid=trent))(objectclass=posixaccount))", numResponses: "3"},
  225. compileSearchFilterTest{name: "notOk", filterStr: "(!(uid=ned))", numResponses: "3"},
  226. compileSearchFilterTest{name: "notOk", filterStr: "(!(uid=foo))", numResponses: "4"},
  227. compileSearchFilterTest{name: "notAndOrOk", filterStr: "(&(|(uid=ned)(uid=trent))(!(objectclass=posixgroup)))", numResponses: "3"},
  228. /*
  229. compileSearchFilterTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings},
  230. compileSearchFilterTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings},
  231. compileSearchFilterTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings},
  232. compileSearchFilterTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual},
  233. compileSearchFilterTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual},
  234. compileSearchFilterTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch},
  235. */
  236. }
  237. /////////////////////////
  238. func TestSearchFiltering(t *testing.T) {
  239. quit := make(chan bool)
  240. done := make(chan bool)
  241. go func() {
  242. s := NewServer()
  243. s.EnforceLDAP = true
  244. s.QuitChannel(quit)
  245. s.SearchFunc("", searchSimple{})
  246. s.BindFunc("", bindSimple{})
  247. if err := s.ListenAndServe(listenString); err != nil {
  248. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  249. }
  250. }()
  251. for _, i := range searchFilterTestFilters {
  252. t.Log(i.name)
  253. go func() {
  254. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  255. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", i.filterStr)
  256. out, _ := cmd.CombinedOutput()
  257. if !strings.Contains(string(out), "numResponses: "+i.numResponses) {
  258. t.Errorf("ldapsearch failed - expected numResponses==%s: %v", i.numResponses, string(out))
  259. }
  260. done <- true
  261. }()
  262. select {
  263. case <-done:
  264. case <-time.After(timeout):
  265. t.Errorf("ldapsearch command timed out")
  266. }
  267. }
  268. quit <- true
  269. }
  270. /////////////////////////
  271. func TestSearchAttributes(t *testing.T) {
  272. quit := make(chan bool)
  273. done := make(chan bool)
  274. go func() {
  275. s := NewServer()
  276. s.EnforceLDAP = true
  277. s.QuitChannel(quit)
  278. s.SearchFunc("", searchSimple{})
  279. s.BindFunc("", bindSimple{})
  280. if err := s.ListenAndServe(listenString); err != nil {
  281. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  282. }
  283. }()
  284. go func() {
  285. filterString := ""
  286. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  287. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", filterString, "cn")
  288. out, _ := cmd.CombinedOutput()
  289. if !strings.Contains(string(out), "dn: cn=ned,o=testers,c=test") {
  290. t.Errorf("ldapsearch failed - missing requested DN attribute: %v", string(out))
  291. }
  292. if !strings.Contains(string(out), "cn: ned") {
  293. t.Errorf("ldapsearch failed - missing requested CN attribute: %v", string(out))
  294. }
  295. if strings.Contains(string(out), "uidNumber") {
  296. t.Errorf("ldapsearch failed - uidNumber attr should not be displayed: %v", string(out))
  297. }
  298. if strings.Contains(string(out), "accountstatus") {
  299. t.Errorf("ldapsearch failed - accountstatus attr should not be displayed: %v", string(out))
  300. }
  301. done <- true
  302. }()
  303. select {
  304. case <-done:
  305. case <-time.After(timeout):
  306. t.Errorf("ldapsearch command timed out")
  307. }
  308. quit <- true
  309. }
  310. func TestSearchAllUserAttributes(t *testing.T) {
  311. quit := make(chan bool)
  312. done := make(chan bool)
  313. go func() {
  314. s := NewServer()
  315. s.EnforceLDAP = true
  316. s.QuitChannel(quit)
  317. s.SearchFunc("", searchSimple{})
  318. s.BindFunc("", bindSimple{})
  319. if err := s.ListenAndServe(listenString); err != nil {
  320. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  321. }
  322. }()
  323. go func() {
  324. filterString := ""
  325. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  326. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", filterString, "*")
  327. out, _ := cmd.CombinedOutput()
  328. if !strings.Contains(string(out), "dn: cn=ned,o=testers,c=test") {
  329. t.Errorf("ldapsearch failed - missing requested DN attribute: %v", string(out))
  330. }
  331. if !strings.Contains(string(out), "cn: ned") {
  332. t.Errorf("ldapsearch failed - missing requested CN attribute: %v", string(out))
  333. }
  334. if !strings.Contains(string(out), "uidNumber") {
  335. t.Errorf("ldapsearch failed - missing requested uidNumber attribute: %v", string(out))
  336. }
  337. if !strings.Contains(string(out), "accountstatus") {
  338. t.Errorf("ldapsearch failed - missing requested accountstatus attribute: %v", string(out))
  339. }
  340. if !strings.Contains(string(out), "o: ate") {
  341. t.Errorf("ldapsearch failed - missing requested o attribute: %v", string(out))
  342. }
  343. if !strings.Contains(string(out), "description") {
  344. t.Errorf("ldapsearch failed - missing requested description attribute: %v", string(out))
  345. }
  346. if !strings.Contains(string(out), "objectclass") {
  347. t.Errorf("ldapsearch failed - missing requested objectclass attribute: %v", string(out))
  348. }
  349. done <- true
  350. }()
  351. select {
  352. case <-done:
  353. case <-time.After(timeout):
  354. t.Errorf("ldapsearch command timed out")
  355. }
  356. quit <- true
  357. }
  358. /////////////////////////
  359. func TestSearchScope(t *testing.T) {
  360. quit := make(chan bool)
  361. done := make(chan bool)
  362. go func() {
  363. s := NewServer()
  364. s.EnforceLDAP = true
  365. s.QuitChannel(quit)
  366. s.SearchFunc("", searchSimple{})
  367. s.BindFunc("", bindSimple{})
  368. if err := s.ListenAndServe(listenString); err != nil {
  369. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  370. }
  371. }()
  372. go func() {
  373. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  374. "-b", "c=test", "-D", "cn=testy,o=testers,c=test", "-w", "iLike2test", "-s", "sub", "cn=trent")
  375. out, _ := cmd.CombinedOutput()
  376. if !strings.Contains(string(out), "dn: cn=trent,o=testers,c=test") {
  377. t.Errorf("ldapsearch 'sub' scope failed - didn't find expected DN: %v", string(out))
  378. }
  379. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  380. "-b", "o=testers,c=test", "-D", "cn=testy,o=testers,c=test", "-w", "iLike2test", "-s", "one", "cn=trent")
  381. out, _ = cmd.CombinedOutput()
  382. if !strings.Contains(string(out), "dn: cn=trent,o=testers,c=test") {
  383. t.Errorf("ldapsearch 'one' scope failed - didn't find expected DN: %v", string(out))
  384. }
  385. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  386. "-b", "c=test", "-D", "cn=testy,o=testers,c=test", "-w", "iLike2test", "-s", "one", "cn=trent")
  387. out, _ = cmd.CombinedOutput()
  388. if strings.Contains(string(out), "dn: cn=trent,o=testers,c=test") {
  389. t.Errorf("ldapsearch 'one' scope failed - found unexpected DN: %v", string(out))
  390. }
  391. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  392. "-b", "cn=trent,o=testers,c=test", "-D", "cn=testy,o=testers,c=test", "-w", "iLike2test", "-s", "base", "cn=trent")
  393. out, _ = cmd.CombinedOutput()
  394. if !strings.Contains(string(out), "dn: cn=trent,o=testers,c=test") {
  395. t.Errorf("ldapsearch 'base' scope failed - didn't find expected DN: %v", string(out))
  396. }
  397. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  398. "-b", "o=testers,c=test", "-D", "cn=testy,o=testers,c=test", "-w", "iLike2test", "-s", "base", "cn=trent")
  399. out, _ = cmd.CombinedOutput()
  400. if strings.Contains(string(out), "dn: cn=trent,o=testers,c=test") {
  401. t.Errorf("ldapsearch 'base' scope failed - found unexpected DN: %v", string(out))
  402. }
  403. done <- true
  404. }()
  405. select {
  406. case <-done:
  407. case <-time.After(timeout):
  408. t.Errorf("ldapsearch command timed out")
  409. }
  410. quit <- true
  411. }
  412. func TestSearchControls(t *testing.T) {
  413. quit := make(chan bool)
  414. done := make(chan bool)
  415. go func() {
  416. s := NewServer()
  417. s.QuitChannel(quit)
  418. s.SearchFunc("", searchControls{})
  419. s.BindFunc("", bindSimple{})
  420. if err := s.ListenAndServe(listenString); err != nil {
  421. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  422. }
  423. }()
  424. serverBaseDN := "o=testers,c=test"
  425. go func() {
  426. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  427. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test", "-e", "1.2.3.4.5")
  428. out, _ := cmd.CombinedOutput()
  429. if !strings.Contains(string(out), "dn: cn=hamburger,o=testers,c=testz") {
  430. t.Errorf("ldapsearch with control failed: %v", string(out))
  431. }
  432. if !strings.Contains(string(out), "result: 0 Success") {
  433. t.Errorf("ldapsearch with control failed: %v", string(out))
  434. }
  435. if !strings.Contains(string(out), "numResponses: 2") {
  436. t.Errorf("ldapsearch with control failed: %v", string(out))
  437. }
  438. cmd = exec.Command("ldapsearch", "-H", ldapURL, "-x",
  439. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test")
  440. out, _ = cmd.CombinedOutput()
  441. if strings.Contains(string(out), "dn: cn=hamburger,o=testers,c=testz") {
  442. t.Errorf("ldapsearch without control failed: %v", string(out))
  443. }
  444. if !strings.Contains(string(out), "result: 0 Success") {
  445. t.Errorf("ldapsearch without control failed: %v", string(out))
  446. }
  447. if !strings.Contains(string(out), "numResponses: 1") {
  448. t.Errorf("ldapsearch without control failed: %v", string(out))
  449. }
  450. done <- true
  451. }()
  452. select {
  453. case <-done:
  454. case <-time.After(timeout):
  455. t.Errorf("ldapsearch command timed out")
  456. }
  457. quit <- true
  458. }