server_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package ldap
  2. import (
  3. "bytes"
  4. "log"
  5. "net"
  6. "os/exec"
  7. "strings"
  8. "testing"
  9. "time"
  10. )
  11. var listenString = "localhost:3389"
  12. var ldapURL = "ldap://" + listenString
  13. var timeout = 400 * time.Millisecond
  14. var serverBaseDN = "o=testers,c=test"
  15. /////////////////////////
  16. func TestBindAnonOK(t *testing.T) {
  17. quit := make(chan bool)
  18. done := make(chan bool)
  19. go func() {
  20. s := NewServer()
  21. s.QuitChannel(quit)
  22. s.BindFunc("", bindAnonOK{})
  23. if err := s.ListenAndServe(listenString); err != nil {
  24. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  25. }
  26. }()
  27. go func() {
  28. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=test")
  29. out, _ := cmd.CombinedOutput()
  30. if !strings.Contains(string(out), "result: 0 Success") {
  31. t.Errorf("ldapsearch failed: %v", string(out))
  32. }
  33. done <- true
  34. }()
  35. select {
  36. case <-done:
  37. case <-time.After(timeout):
  38. t.Errorf("ldapsearch command timed out")
  39. }
  40. quit <- true
  41. }
  42. /////////////////////////
  43. func TestBindAnonFail(t *testing.T) {
  44. quit := make(chan bool)
  45. done := make(chan bool)
  46. go func() {
  47. s := NewServer()
  48. s.QuitChannel(quit)
  49. if err := s.ListenAndServe(listenString); err != nil {
  50. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  51. }
  52. }()
  53. time.Sleep(timeout)
  54. go func() {
  55. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=test")
  56. out, _ := cmd.CombinedOutput()
  57. if !strings.Contains(string(out), "ldap_bind: Invalid credentials (49)") {
  58. t.Errorf("ldapsearch failed: %v", string(out))
  59. }
  60. done <- true
  61. }()
  62. select {
  63. case <-done:
  64. case <-time.After(timeout):
  65. t.Errorf("ldapsearch command timed out")
  66. }
  67. time.Sleep(timeout)
  68. quit <- true
  69. }
  70. /////////////////////////
  71. func TestBindSimpleOK(t *testing.T) {
  72. quit := make(chan bool)
  73. done := make(chan bool)
  74. go func() {
  75. s := NewServer()
  76. s.QuitChannel(quit)
  77. s.SearchFunc("", searchSimple{})
  78. s.BindFunc("", bindSimple{})
  79. if err := s.ListenAndServe(listenString); err != nil {
  80. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  81. }
  82. }()
  83. serverBaseDN := "o=testers,c=test"
  84. go func() {
  85. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  86. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "iLike2test")
  87. out, _ := cmd.CombinedOutput()
  88. if !strings.Contains(string(out), "result: 0 Success") {
  89. t.Errorf("ldapsearch failed: %v", string(out))
  90. }
  91. done <- true
  92. }()
  93. select {
  94. case <-done:
  95. case <-time.After(timeout):
  96. t.Errorf("ldapsearch command timed out")
  97. }
  98. quit <- true
  99. }
  100. /////////////////////////
  101. func TestBindSimpleFailBadPw(t *testing.T) {
  102. quit := make(chan bool)
  103. done := make(chan bool)
  104. go func() {
  105. s := NewServer()
  106. s.QuitChannel(quit)
  107. s.BindFunc("", bindSimple{})
  108. if err := s.ListenAndServe(listenString); err != nil {
  109. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  110. }
  111. }()
  112. serverBaseDN := "o=testers,c=test"
  113. go func() {
  114. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  115. "-b", serverBaseDN, "-D", "cn=testy,"+serverBaseDN, "-w", "BADPassword")
  116. out, _ := cmd.CombinedOutput()
  117. if !strings.Contains(string(out), "ldap_bind: Invalid credentials (49)") {
  118. t.Errorf("ldapsearch succeeded - should have failed: %v", string(out))
  119. }
  120. done <- true
  121. }()
  122. select {
  123. case <-done:
  124. case <-time.After(timeout):
  125. t.Errorf("ldapsearch command timed out")
  126. }
  127. quit <- true
  128. }
  129. /////////////////////////
  130. func TestBindSimpleFailBadDn(t *testing.T) {
  131. quit := make(chan bool)
  132. done := make(chan bool)
  133. go func() {
  134. s := NewServer()
  135. s.QuitChannel(quit)
  136. s.BindFunc("", bindSimple{})
  137. if err := s.ListenAndServe(listenString); err != nil {
  138. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  139. }
  140. }()
  141. serverBaseDN := "o=testers,c=test"
  142. go func() {
  143. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
  144. "-b", serverBaseDN, "-D", "cn=testoy,"+serverBaseDN, "-w", "iLike2test")
  145. out, _ := cmd.CombinedOutput()
  146. if string(out) != "ldap_bind: Invalid credentials (49)\n" {
  147. t.Errorf("ldapsearch succeeded - should have failed: %v", string(out))
  148. }
  149. done <- true
  150. }()
  151. select {
  152. case <-done:
  153. case <-time.After(timeout):
  154. t.Errorf("ldapsearch command timed out")
  155. }
  156. quit <- true
  157. }
  158. /////////////////////////
  159. func TestBindSSL(t *testing.T) {
  160. ldapURLSSL := "ldaps://" + listenString
  161. longerTimeout := 300 * time.Millisecond
  162. quit := make(chan bool)
  163. done := make(chan bool)
  164. go func() {
  165. s := NewServer()
  166. s.QuitChannel(quit)
  167. s.BindFunc("", bindAnonOK{})
  168. if err := s.ListenAndServeTLS(listenString, "tests/cert_DONOTUSE.pem", "tests/key_DONOTUSE.pem"); err != nil {
  169. t.Errorf("s.ListenAndServeTLS failed: %s", err.Error())
  170. }
  171. }()
  172. go func() {
  173. time.Sleep(longerTimeout * 2)
  174. cmd := exec.Command("ldapsearch", "-H", ldapURLSSL, "-x", "-b", "o=testers,c=test")
  175. out, _ := cmd.CombinedOutput()
  176. if !strings.Contains(string(out), "result: 0 Success") {
  177. t.Errorf("ldapsearch failed: %v", string(out))
  178. }
  179. done <- true
  180. }()
  181. select {
  182. case <-done:
  183. case <-time.After(longerTimeout * 2):
  184. t.Errorf("ldapsearch command timed out")
  185. }
  186. quit <- true
  187. }
  188. /////////////////////////
  189. func TestBindPanic(t *testing.T) {
  190. quit := make(chan bool)
  191. done := make(chan bool)
  192. go func() {
  193. s := NewServer()
  194. s.QuitChannel(quit)
  195. s.BindFunc("", bindPanic{})
  196. if err := s.ListenAndServe(listenString); err != nil {
  197. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  198. }
  199. }()
  200. go func() {
  201. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=test")
  202. out, _ := cmd.CombinedOutput()
  203. if !strings.Contains(string(out), "ldap_bind: Operations error") {
  204. t.Errorf("ldapsearch should have returned operations error due to panic: %v", string(out))
  205. }
  206. done <- true
  207. }()
  208. select {
  209. case <-done:
  210. case <-time.After(timeout):
  211. t.Errorf("ldapsearch command timed out")
  212. }
  213. quit <- true
  214. }
  215. /////////////////////////
  216. type testStatsWriter struct {
  217. buffer *bytes.Buffer
  218. }
  219. func (tsw testStatsWriter) Write(buf []byte) (int, error) {
  220. tsw.buffer.Write(buf)
  221. return len(buf), nil
  222. }
  223. func TestSearchStats(t *testing.T) {
  224. w := testStatsWriter{&bytes.Buffer{}}
  225. log.SetOutput(w)
  226. quit := make(chan bool)
  227. done := make(chan bool)
  228. s := NewServer()
  229. go func() {
  230. s.QuitChannel(quit)
  231. s.SearchFunc("", searchSimple{})
  232. s.BindFunc("", bindAnonOK{})
  233. s.SetStats(true)
  234. if err := s.ListenAndServe(listenString); err != nil {
  235. t.Errorf("s.ListenAndServe failed: %s", err.Error())
  236. }
  237. }()
  238. go func() {
  239. cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x", "-b", "o=testers,c=test")
  240. out, _ := cmd.CombinedOutput()
  241. if !strings.Contains(string(out), "result: 0 Success") {
  242. t.Errorf("ldapsearch failed: %v", string(out))
  243. }
  244. done <- true
  245. }()
  246. select {
  247. case <-done:
  248. case <-time.After(timeout):
  249. t.Errorf("ldapsearch command timed out")
  250. }
  251. stats := s.GetStats()
  252. log.Println(stats)
  253. if stats.Conns != 1 || stats.Binds != 1 {
  254. t.Errorf("Stats data missing or incorrect: %v", w.buffer.String())
  255. }
  256. quit <- true
  257. }
  258. /////////////////////////
  259. type bindAnonOK struct {
  260. }
  261. func (b bindAnonOK) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error) {
  262. if bindDN == "" && bindSimplePw == "" {
  263. return LDAPResultSuccess, nil
  264. }
  265. return LDAPResultInvalidCredentials, nil
  266. }
  267. type bindSimple struct {
  268. }
  269. func (b bindSimple) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error) {
  270. if bindDN == "cn=testy,o=testers,c=test" && bindSimplePw == "iLike2test" {
  271. return LDAPResultSuccess, nil
  272. }
  273. return LDAPResultInvalidCredentials, nil
  274. }
  275. type bindSimple2 struct {
  276. }
  277. func (b bindSimple2) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error) {
  278. if bindDN == "cn=testy,o=testers,c=testz" && bindSimplePw == "ZLike2test" {
  279. return LDAPResultSuccess, nil
  280. }
  281. return LDAPResultInvalidCredentials, nil
  282. }
  283. type bindPanic struct {
  284. }
  285. func (b bindPanic) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error) {
  286. panic("test panic at the disco")
  287. return LDAPResultInvalidCredentials, nil
  288. }
  289. type searchSimple struct {
  290. }
  291. func (s searchSimple) Search(boundDN string, searchReq SearchRequest, conn net.Conn) (ServerSearchResult, error) {
  292. entries := []*Entry{
  293. &Entry{"cn=ned,o=testers,c=test", []*EntryAttribute{
  294. &EntryAttribute{"cn", []string{"ned"}},
  295. &EntryAttribute{"o", []string{"ate"}},
  296. &EntryAttribute{"uidNumber", []string{"5000"}},
  297. &EntryAttribute{"accountstatus", []string{"active"}},
  298. &EntryAttribute{"uid", []string{"ned"}},
  299. &EntryAttribute{"description", []string{"ned via sa"}},
  300. &EntryAttribute{"objectclass", []string{"posixaccount"}},
  301. }},
  302. &Entry{"cn=trent,o=testers,c=test", []*EntryAttribute{
  303. &EntryAttribute{"cn", []string{"trent"}},
  304. &EntryAttribute{"o", []string{"ate"}},
  305. &EntryAttribute{"uidNumber", []string{"5005"}},
  306. &EntryAttribute{"accountstatus", []string{"active"}},
  307. &EntryAttribute{"uid", []string{"trent"}},
  308. &EntryAttribute{"description", []string{"trent via sa"}},
  309. &EntryAttribute{"objectclass", []string{"posixaccount"}},
  310. }},
  311. &Entry{"cn=randy,o=testers,c=test", []*EntryAttribute{
  312. &EntryAttribute{"cn", []string{"randy"}},
  313. &EntryAttribute{"o", []string{"ate"}},
  314. &EntryAttribute{"uidNumber", []string{"5555"}},
  315. &EntryAttribute{"accountstatus", []string{"active"}},
  316. &EntryAttribute{"uid", []string{"randy"}},
  317. &EntryAttribute{"objectclass", []string{"posixaccount"}},
  318. }},
  319. }
  320. return ServerSearchResult{entries, []string{}, []Control{}, LDAPResultSuccess}, nil
  321. }
  322. type searchSimple2 struct {
  323. }
  324. func (s searchSimple2) Search(boundDN string, searchReq SearchRequest, conn net.Conn) (ServerSearchResult, error) {
  325. entries := []*Entry{
  326. &Entry{"cn=hamburger,o=testers,c=testz", []*EntryAttribute{
  327. &EntryAttribute{"cn", []string{"hamburger"}},
  328. &EntryAttribute{"o", []string{"testers"}},
  329. &EntryAttribute{"uidNumber", []string{"5000"}},
  330. &EntryAttribute{"accountstatus", []string{"active"}},
  331. &EntryAttribute{"uid", []string{"hamburger"}},
  332. &EntryAttribute{"objectclass", []string{"posixaccount"}},
  333. }},
  334. }
  335. return ServerSearchResult{entries, []string{}, []Control{}, LDAPResultSuccess}, nil
  336. }
  337. type searchPanic struct {
  338. }
  339. func (s searchPanic) Search(boundDN string, searchReq SearchRequest, conn net.Conn) (ServerSearchResult, error) {
  340. entries := []*Entry{}
  341. panic("this is a test panic")
  342. return ServerSearchResult{entries, []string{}, []Control{}, LDAPResultSuccess}, nil
  343. }
  344. type searchControls struct {
  345. }
  346. func (s searchControls) Search(boundDN string, searchReq SearchRequest, conn net.Conn) (ServerSearchResult, error) {
  347. entries := []*Entry{}
  348. if len(searchReq.Controls) == 1 && searchReq.Controls[0].GetControlType() == "1.2.3.4.5" {
  349. newEntry := &Entry{"cn=hamburger,o=testers,c=testz", []*EntryAttribute{
  350. &EntryAttribute{"cn", []string{"hamburger"}},
  351. &EntryAttribute{"o", []string{"testers"}},
  352. &EntryAttribute{"uidNumber", []string{"5000"}},
  353. &EntryAttribute{"accountstatus", []string{"active"}},
  354. &EntryAttribute{"uid", []string{"hamburger"}},
  355. &EntryAttribute{"objectclass", []string{"posixaccount"}},
  356. }}
  357. entries = append(entries, newEntry)
  358. }
  359. return ServerSearchResult{entries, []string{}, []Control{}, LDAPResultSuccess}, nil
  360. }