From 1c9220e04016d035c3c688c315ceabe274f45dfc Mon Sep 17 00:00:00 2001 From: "kali kaneko (leap communications)" Date: Fri, 24 Jan 2020 22:34:09 -0600 Subject: initial sip implementation --- cmd/vpnweb/vpnweb.go | 4 +- go.mod | 2 + go.sum | 4 ++ pkg/auth/middleware.go | 8 +-- pkg/auth/sip2/client.go | 74 +++++++++++++++++++++++++ pkg/auth/sip2/spec.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ pkg/auth/sip2/telnet.go | 43 +++++++++++++++ 7 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 pkg/auth/sip2/client.go create mode 100644 pkg/auth/sip2/spec.go create mode 100644 pkg/auth/sip2/telnet.go 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 */ diff --git a/go.mod b/go.mod index 11be102..55f4406 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 7211e92..6f0e519 100644 --- a/go.sum +++ b/go.sum @@ -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) +} -- cgit v1.2.3