Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mmitton committed Feb 18, 2011
0 parents commit 1094e1b
Show file tree
Hide file tree
Showing 9 changed files with 1,382 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2009 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

include $(GOROOT)/src/Make.inc

TARG=github.com/mmitton/ldap
GOFILES=\
bind.go\
conn.go\
control.go\
filter.go\
ldap.go\
search.go\

include $(GOROOT)/src/Make.pkg
27 changes: 27 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Basic LDAP v3 functionality for the GO programming language.

Required Librarys:
github.com/mmitton/asn1/ber

Working:
Connecting to LDAP server
Binding to LDAP server
Searching for entries
Compiling string filters to LDAP filters
Paging Search Results
Mulitple internal goroutines to handle network traffic
Makes library goroutine safe
Can perform multiple search requests at the same time and return
the results to the proper goroutine. All requests are blocking
requests, so the goroutine does not need special handling

Tests Implemented:
Filter Compile / Decompile

TODO:
Modify Requests / Responses
Add Requests / Responses
Delete Requests / Responses
Modify DN Requests / Responses
Compare Requests / Responses
Implement Tests / Benchmarks
50 changes: 50 additions & 0 deletions bind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// File contains Bind functionality
package ldap

import (
"github.com/mmitton/asn1-ber"
"os"
)

func (l *Conn) Bind( username, password string ) *Error {
messageID := l.nextMessageID()

packet := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request" )
packet.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, messageID, "MessageID" ) )
bindRequest := ber.Encode( ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request" )
bindRequest.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, 3, "Version" ) )
bindRequest.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, username, "User Name" ) )
bindRequest.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, 0, password, "Password" ) )
packet.AppendChild( bindRequest )

if l.Debug {
ber.PrintPacket( packet )
}

channel, err := l.sendMessage( packet )
if err != nil {
return err
}
if channel == nil {
return NewError( ErrorNetwork, os.NewError( "Could not send message" ) )
}
defer l.finishMessage( messageID )
packet = <-channel

if packet != nil {
return NewError( ErrorNetwork, os.NewError( "Could not retrieve response" ) )
}

if l.Debug {
if err := addLDAPDescriptions( packet ); err != nil {
return NewError( ErrorDebugging, err )
}
ber.PrintPacket( packet )
}

return nil
}
270 changes: 270 additions & 0 deletions conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package provides LDAP client functions.
package ldap

import (
"github.com/mmitton/asn1-ber"
"crypto/tls"
"fmt"
"net"
"os"
)

// LDAP Connection
type Conn struct {
conn net.Conn
isSSL bool
Debug bool

chanResults map[ uint64 ] chan *ber.Packet
chanProcessMessage chan *messagePacket
chanMessageID chan uint64
}

// Dial connects to the given address on the given network using net.Dial
// and then returns a new Conn for the connection.
func Dial(network, addr string) (*Conn, *Error) {
c, err := net.Dial(network, "", addr)
if err != nil {
return nil, NewError( ErrorNetwork, err )
}
conn := NewConn(c)
conn.start()
return conn, nil
}

// Dial connects to the given address on the given network using net.Dial
// and then sets up SSL connection and returns a new Conn for the connection.
func DialSSL(network, addr string) (*Conn, *Error) {
c, err := tls.Dial(network, "", addr, nil)
if err != nil {
return nil, NewError( ErrorNetwork, err )
}
conn := NewConn(c)
conn.isSSL = true

conn.start()
return conn, nil
}

// Dial connects to the given address on the given network using net.Dial
// and then starts a TLS session and returns a new Conn for the connection.
func DialTLS(network, addr string) (*Conn, *Error) {
c, err := net.Dial(network, "", addr)
if err != nil {
return nil, NewError( ErrorNetwork, err )
}
conn := NewConn(c)

err = conn.startTLS()
if err != nil {
conn.Close()
return nil, NewError( ErrorNetwork, err )
}
conn.start()
return conn, nil
}

// NewConn returns a new Conn using conn for network I/O.
func NewConn(conn net.Conn) *Conn {
return &Conn{
conn: conn,
isSSL: false,
Debug: false,
chanResults: map[uint64] chan *ber.Packet{},
chanProcessMessage: make( chan *messagePacket ),
chanMessageID: make( chan uint64 ),
}
}

func (l *Conn) start() {
go l.reader()
go l.processMessages()
}

// Close closes the connection.
func (l *Conn) Close() *Error {
if l.chanProcessMessage != nil {
message_packet := &messagePacket{ Op: MessageQuit }
l.chanProcessMessage <- message_packet
l.chanProcessMessage = nil
}

if l.conn != nil {
err := l.conn.Close()
if err != nil {
return NewError( ErrorNetwork, err )
}
l.conn = nil
}
return nil
}

// Returns the next available messageID
func (l *Conn) nextMessageID() uint64 {
messageID := <-l.chanMessageID
return messageID
}

// StartTLS sends the command to start a TLS session and then creates a new TLS Client
func (l *Conn) startTLS() *Error {
messageID := l.nextMessageID()

if l.isSSL {
return NewError( ErrorNetwork, os.NewError( "Already encrypted" ) )
}

packet := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request" )
packet.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, messageID, "MessageID" ) )
startTLS := ber.Encode( ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS" )
startTLS.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command" ) )
packet.AppendChild( startTLS )
if l.Debug {
ber.PrintPacket( packet )
}

_, err := l.conn.Write( packet.Bytes() )
if err != nil {
return NewError( ErrorNetwork, err )
}

packet, err = ber.ReadPacket( l.conn )
if err != nil {
return NewError( ErrorNetwork, err )
}

if l.Debug {
if err := addLDAPDescriptions( packet ); err != nil {
return NewError( ErrorDebugging, err )
}
ber.PrintPacket( packet )
}

if packet.Children[ 1 ].Children[ 0 ].Value.(uint64) == 0 {
conn := tls.Client( l.conn, nil )
l.isSSL = true
l.conn = conn
}

return nil
}

const (
MessageQuit = 0
MessageRequest = 1
MessageResponse = 2
MessageFinish = 3
)

type messagePacket struct {
Op int
MessageID uint64
Packet *ber.Packet
Channel chan *ber.Packet
}

func (l *Conn) sendMessage( p *ber.Packet ) (out chan *ber.Packet, err *Error) {
message_id := p.Children[ 0 ].Value.(uint64)
out = make(chan *ber.Packet)

message_packet := &messagePacket{ Op: MessageRequest, MessageID: message_id, Packet: p, Channel: out }
if l.chanProcessMessage == nil {
err = NewError( ErrorNetwork, os.NewError( "Connection closed" ) )
return
}
l.chanProcessMessage <- message_packet
return
}

func (l *Conn) processMessages() {
defer l.closeAllChannels()

var message_id uint64 = 1
var message_packet *messagePacket
for {
select {
case l.chanMessageID <- message_id:
if l.conn == nil {
return
}
message_id++
case message_packet = <-l.chanProcessMessage:
if l.conn == nil {
return
}
switch message_packet.Op {
case MessageQuit:
// Close all channels and quit
if l.Debug {
fmt.Printf( "Shutting down\n" )
}
return
case MessageRequest:
// Add to message list and write to network
if l.Debug {
fmt.Printf( "Sending message %d\n", message_packet.MessageID )
}
l.chanResults[ message_packet.MessageID ] = message_packet.Channel
l.conn.Write( message_packet.Packet.Bytes() )
case MessageResponse:
// Pass back to waiting goroutine
if l.Debug {
fmt.Printf( "Receiving message %d\n", message_packet.MessageID )
}
chanResult := l.chanResults[ message_packet.MessageID ]
if chanResult == nil {
fmt.Printf( "Unexpected Message Result: %d", message_id )
} else {
chanResult <- message_packet.Packet
}
case MessageFinish:
// Remove from message list
if l.Debug {
fmt.Printf( "Finished message %d\n", message_packet.MessageID )
}
l.chanResults[ message_packet.MessageID ] = nil, false
}
}
}
}

func (l *Conn) closeAllChannels() {
for MessageID, Channel := range l.chanResults {
if l.Debug {
fmt.Printf( "Closing channel for MessageID %d\n", MessageID );
}
close( Channel )
l.chanResults[ MessageID ] = nil, false
}
close( l.chanMessageID )
l.chanMessageID = nil
}

func (l *Conn) finishMessage( MessageID uint64 ) {
message_packet := &messagePacket{ Op: MessageFinish, MessageID: MessageID }
if l.chanProcessMessage != nil {
l.chanProcessMessage <- message_packet
}
}

func (l *Conn) reader() {
for {
p, err := ber.ReadPacket( l.conn )
if err != nil {
if l.Debug {
fmt.Printf( "ldap.reader: %s\n", err.String() )
}
break
}

message_id := p.Children[ 0 ].Value.(uint64)
message_packet := &messagePacket{ Op: MessageResponse, MessageID: message_id, Packet: p }
l.chanProcessMessage <- message_packet
}

l.Close()
}

Loading

0 comments on commit 1094e1b

Please sign in to comment.