diff options
| -rw-r--r-- | cmd/vpnweb/vpnweb.go | 4 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | pkg/auth/middleware.go | 8 | ||||
| -rw-r--r-- | pkg/auth/sip2/client.go | 74 | ||||
| -rw-r--r-- | pkg/auth/sip2/spec.go | 141 | ||||
| -rw-r--r-- | pkg/auth/sip2/telnet.go | 43 | 
7 files changed, 270 insertions, 6 deletions
| diff --git a/cmd/vpnweb/vpnweb.go b/cmd/vpnweb/vpnweb.go index 2305d98..ff25e09 100644 --- a/cmd/vpnweb/vpnweb.go +++ b/cmd/vpnweb/vpnweb.go @@ -21,11 +21,11 @@ func main() {  	/* protected routes */  	/* TODO ---- -	http.HandleFunc("/3/auth", auth.AuthMiddleware(opts.Auth))  	http.HandleFunc("/3/refresh-token", auth.RefreshAuthMiddleware(opts.Auth))  	*/ -	http.Handle("/3/cert", auth.AuthMiddleware(opts.Auth, ch)) +	http.Handle("/3/cert", auth.RestrictedMiddleware(opts.Auth, ch)) +	http.Handle("/3/auth", auth.Authenticator(opts.Auth))  	/* static files */ @@ -8,5 +8,7 @@ require (  	github.com/dgrijalva/jwt-go v3.2.0+incompatible  	github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect  	github.com/gorilla/mux v1.7.3 // indirect +	github.com/reiver/go-oi v1.0.0 // indirect +	github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e  	github.com/urfave/negroni v1.0.0 // indirect  ) @@ -8,5 +8,9 @@ github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iauee  github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=  github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=  github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM= +github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI= +github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas= +github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg=  github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=  github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= diff --git a/pkg/auth/middleware.go b/pkg/auth/middleware.go index 5fe0ab7..a183c1b 100644 --- a/pkg/auth/middleware.go +++ b/pkg/auth/middleware.go @@ -11,14 +11,13 @@ import (  const anonAuth string = "anon"  const sipAuth string = "sip" +/* FIXME -- get this from configuration variables */  var jwtSecret = []byte("somethingverysecret") -func getHandler(ch web.CertHandler) func(w http.ResponseWriter, r *http.Request) { -	return ch.CertResponder +func Authenticator(auth string) {  } -//func AuthMiddleware(auth string, ch web.CertHandler) func(w http.ResponseWriter, r *http.Request) { -func AuthMiddleware(auth string, ch web.CertHandler) http.Handler { +func RestrictedMiddleware(auth string, ch web.CertHandler) http.Handler {  	jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{  		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { @@ -38,5 +37,6 @@ func AuthMiddleware(auth string, ch web.CertHandler) http.Handler {  	default:  		log.Fatal("Unknown auth module: '", auth, "'. Should be one of: ", anonAuth, ", ", sipAuth, ".")  	} +	// should not get here  	return nil  } diff --git a/pkg/auth/sip2/client.go b/pkg/auth/sip2/client.go new file mode 100644 index 0000000..fbdeded --- /dev/null +++ b/pkg/auth/sip2/client.go @@ -0,0 +1,74 @@ +package sip2 + +import ( +	"fmt" +	"github.com/reiver/go-telnet" +	"log" +	"time" +) + +const loginRequestTemplate string = "9300CN%s|CO%s|CP%s|" +const statusRequestTemplate string = "23000%s    %sAO%s|AA%s|AD%s|" + +type Client struct { +	Host     string +	Port     string +	location string +	conn     *telnet.Conn +	parser   *Parser +} + +func NewClient(host, port, location string) Client { +	c := Client{host, port, location, nil, nil} +	c.parser = getParser() +	return c +} + +func (c *Client) Connect() (bool, error) { +	conn, err := telnet.DialTo(c.Host + ":" + c.Port) +	if nil != err { +		log.Println(log.Printf("error: %v", err)) +		return false, err +	} +	c.conn = conn +	return true, nil +} + +func (c *Client) Login(user, pass string) bool { +	loginStr := fmt.Sprintf(loginRequestTemplate, user, pass, c.location) +	if nil == c.conn { +		fmt.Println("error! null connection") +	} +	telnetSend(c.conn, loginStr) +	loginResp := telnetRead(c.conn) +	msg := c.parseResponse(loginResp) +	if value, ok := c.parser.getFixedFieldValue(msg, Ok); ok && value == TRUE { +		return true +	} +	return false +} + +func (c *Client) CheckCredentials(user, passwd string) bool { +	currentTime := time.Now() +	statusRequest := fmt.Sprintf( +		statusRequestTemplate, +		currentTime.Format("20060102"), +		currentTime.Format("150102"), +		c.location, user, passwd) +	telnetSend(c.conn, statusRequest) + +	statusMsg := c.parseResponse(telnetRead(c.conn)) +	if value, ok := c.parser.getFieldValue(statusMsg, ValidPatron); ok && value == YES { +		if value, ok := c.parser.getFieldValue(statusMsg, ValidPatronPassword); ok && value == YES { +			return true +		} +	} + +	// TODO log whatever error we can find (AF, Screen Message, for instance) +	return false +} + +func (c *Client) parseResponse(txt string) *Message { +	msg := c.parser.parseMessage(txt) +	return msg +} diff --git a/pkg/auth/sip2/spec.go b/pkg/auth/sip2/spec.go new file mode 100644 index 0000000..9c4ac48 --- /dev/null +++ b/pkg/auth/sip2/spec.go @@ -0,0 +1,141 @@ +package sip2 + +import ( +	"log" +	"strconv" +	"strings" +) + +type FixedFieldSpec struct { +	length int +	label  string +} + +type FixedField struct { +	spec  FixedFieldSpec +	value string +} + +type VariableFieldSpec struct { +	id    string +	label string +} + +type VariableField struct { +	spec  VariableFieldSpec +	value string +} + +type MessageSpec struct { +	id     int +	label  string +	fields []FixedFieldSpec +} + +type Message struct { +	fields       []VariableField +	fixed_fields []FixedField +	msg_txt      string +} + +type Parser struct { +	getMessageSpecByCode   func(int) MessageSpec +	getVariableFieldByCode func(string) VariableFieldSpec +	getFixedFieldValue     func(*Message, string) (string, bool) +	getFieldValue          func(*Message, string) (string, bool) +	parseMessage           func(string) *Message +} + +const ( +	YES                  string = "Y" +	TRUE                 string = "1" +	Ok                   string = "ok" +	Language             string = "language" +	PatronStatus         string = "patron status" +	Date                 string = "transaction date" +	PatronIdentifier     string = "patron identifier" +	PatronPassword       string = "patron password" +	PersonalName         string = "personal name" +	ScreenMessage        string = "screen message" +	InstitutionId        string = "institution id" +	ValidPatron          string = "valid patron" +	ValidPatronPassword  string = "valid patron password" +	LoginResponse        string = "Login Response" +	PatronStatusResponse string = "Patron Status Response" +) + +func getParser() *Parser { + +	LanguageSpec := FixedFieldSpec{3, Language} +	PatronStatusSpec := FixedFieldSpec{14, PatronStatus} +	DateSpec := FixedFieldSpec{18, Date} +	OkSpec := FixedFieldSpec{1, Ok} + +	msgByCodeMap := map[int]MessageSpec{ +		94: MessageSpec{94, LoginResponse, []FixedFieldSpec{OkSpec}}, +		24: MessageSpec{24, PatronStatusResponse, []FixedFieldSpec{PatronStatusSpec, LanguageSpec, DateSpec}}, +	} + +	variableFieldByCodeMap := map[string]VariableFieldSpec{ +		"AA": VariableFieldSpec{"AA", PatronIdentifier}, +		"AD": VariableFieldSpec{"AD", PatronPassword}, +		"AE": VariableFieldSpec{"AE", PersonalName}, +		"AF": VariableFieldSpec{"AF", ScreenMessage}, +		"AO": VariableFieldSpec{"AO", InstitutionId}, +		"BL": VariableFieldSpec{"BL", ValidPatron}, +		"CQ": VariableFieldSpec{"CQ", ValidPatronPassword}, +	} + +	parser := new(Parser) +	parser.getMessageSpecByCode = func(code int) MessageSpec { +		return msgByCodeMap[code] +	} +	parser.getVariableFieldByCode = func(code string) VariableFieldSpec { +		return variableFieldByCodeMap[code] +	} +	parser.getFixedFieldValue = func(msg *Message, field string) (string, bool) { +		for _, v := range msg.fixed_fields { +			if v.spec.label == field { +				return v.value, true +			} +		} +		return "", false +	} +	parser.getFieldValue = func(msg *Message, field string) (string, bool) { +		for _, v := range msg.fields { +			if v.spec.label == field { +				return v.value, true +			} +		} +		return "", false +	} + +	parser.parseMessage = func(msg string) *Message { +		txt := msg[:len(msg)-len(terminator)] +		code, err := strconv.Atoi(txt[:2]) +		if nil != err { +			log.Println("Error parsing integer: %s", txt[:2]) +		} +		spec := parser.getMessageSpecByCode(code) +		txt = txt[2:] + +		message := new(Message) +		for _, sp := range spec.fields { +			value := txt[:sp.length] +			txt = txt[sp.length:] +			message.fixed_fields = append(message.fixed_fields, FixedField{sp, value}) +		} +		if len(txt) == 0 { +			return message +		} +		for _, part := range strings.Split(txt, "|") { +			if len(part) > 0 { +				part_spec := parser.getVariableFieldByCode(part[:2]) +				value := part[2:] +				message.fields = append(message.fields, VariableField{part_spec, value}) +			} +		} +		return message +	} +	return parser +} diff --git a/pkg/auth/sip2/telnet.go b/pkg/auth/sip2/telnet.go new file mode 100644 index 0000000..b5abd5f --- /dev/null +++ b/pkg/auth/sip2/telnet.go @@ -0,0 +1,43 @@ +package sip2 + +import ( +	"github.com/reiver/go-telnet" +) + +// TODO depends on how terminator is configured -- take it from config file +// const terminator string = "\r\n" +const terminator string = "\r" + +func telnetRead(conn *telnet.Conn) (out string) { +	var buffer [1]byte +	recvData := buffer[:] +	var n int +	var err error + +	for { +		n, err = conn.Read(recvData) +		if n <= 0 || err != nil { +			break +		} else { +			out += string(recvData) +		} +		if len(out) > 1 && out[len(out)-len(terminator):] == terminator { +			break +		} +	} +	return out +} + +func telnetSend(conn *telnet.Conn, command string) { +	var commandBuffer []byte +	for _, char := range command { +		commandBuffer = append(commandBuffer, byte(char)) +	} + +	var crlfBuffer [2]byte = [2]byte{'\r', '\n'} + +	crlf := crlfBuffer[:] + +	conn.Write(commandBuffer) +	conn.Write(crlf) +} | 
