├── CHANGELOG ├── .gitignore ├── example ├── cpe.db.gz ├── agent.go ├── mib_ex.go └── cr_via_xmpp.go ├── www └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ └── glyphicons-halflings-regular.svg ├── .travis.yml ├── cwmp ├── cwmp_test.go └── cwmp.go ├── client ├── mib.go └── client.go ├── mosesacs.go ├── daemon ├── client.go ├── http_client_digest.go ├── daemon.go └── websocket_handler.go ├── LICENSE ├── cli ├── connection.go └── cli.go ├── xmpp └── xmpp.go └── README.md /CHANGELOG: -------------------------------------------------------------------------------- 1 | # MosesACS 0.2.0 2 | 3 | - -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .idea 3 | *.iml 4 | bin/* 5 | .vscode/* 6 | -------------------------------------------------------------------------------- /example/cpe.db.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdir/mosesacs/HEAD/example/cpe.db.gz -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdir/mosesacs/HEAD/www/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdir/mosesacs/HEAD/www/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdir/mosesacs/HEAD/www/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdir/mosesacs/HEAD/www/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /example/agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lucacervasio/mosesacs/client" 5 | ) 6 | 7 | func main() { 8 | agent := cwmpclient.NewClient() 9 | agent.Run() 10 | } 11 | 12 | -------------------------------------------------------------------------------- /example/mib_ex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lucacervasio/mosesacs/client" 6 | ) 7 | 8 | func main() { 9 | m := cwmpclient.NewMib() 10 | fmt.Println(m) 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - tip 6 | 7 | install: 8 | - go get github.com/lucacervasio/liner 9 | - go get github.com/mxk/go-sqlite/sqlite3 10 | 11 | script: go test cwmp/* 12 | -------------------------------------------------------------------------------- /cwmp/cwmp_test.go: -------------------------------------------------------------------------------- 1 | package cwmp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestInformParsing(t *testing.T) { 8 | inform := `blabla` 9 | 10 | if inform != "blabla" { 11 | t.Errorf("Inform can't parse") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/cr_via_xmpp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lucacervasio/mosesacs/xmpp" 5 | "time" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | xmpp.StartClient("acs@mosesacs.org", "password1234", func(str string) { 11 | log.Println(str) 12 | }) 13 | defer xmpp.Close() 14 | xmpp.SendConnectionRequest("cpe1@mosesacs.org/casatua") 15 | time.Sleep(2 * time.Second) 16 | } 17 | -------------------------------------------------------------------------------- /client/mib.go: -------------------------------------------------------------------------------- 1 | package cwmpclient 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type MibValue struct { 8 | } 9 | 10 | type Mib struct { 11 | Tree map[string]string 12 | } 13 | 14 | func NewMib() *Mib { 15 | fmt.Println("creating new struct") 16 | m := &Mib{} 17 | return m 18 | } 19 | 20 | func (mib *Mib) AddSubTree(path string) { 21 | 22 | } 23 | 24 | func (mib *Mib) GetValue(path string) { 25 | 26 | } 27 | 28 | func (mib *Mib) SetValue(path string, value string) { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /mosesacs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/sdir/mosesacs/cli" 9 | "github.com/sdir/mosesacs/daemon" 10 | ) 11 | 12 | func main() { 13 | 14 | port := flag.Int("p", 9292, "Daemon port to listen on") 15 | flDaemon := flag.Bool("d", false, "Enable daemon mode") 16 | flVersion := flag.Bool("v", false, "Version") 17 | flHelp := flag.Bool("h", false, "Help") 18 | flUrl := flag.String("u", "localhost:9292", "Url to connect") 19 | flXmppUser := flag.String("xmpp-user", "", "Xmpp Username") 20 | flXmppPassword := flag.String("xmpp-pass", "", "Xmpp Password") 21 | flag.Parse() 22 | 23 | fmt.Printf("MosesACS %s by Luca Cervasio (C)2014-2016 http://mosesacs.org\n", daemon.Version) 24 | 25 | if *flVersion { 26 | os.Exit(0) 27 | } 28 | 29 | if *flHelp { 30 | flag.Usage() 31 | os.Exit(0) 32 | } 33 | 34 | if *flDaemon { 35 | logger := daemon.BasicWriter{} 36 | daemon.Run(port, &logger, *flXmppUser, *flXmppPassword) 37 | } else { 38 | client.Run(*flUrl) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /daemon/client.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/net/websocket" 6 | "time" 7 | // "github.com/lucacervasio/mosesacs/client" 8 | "encoding/json" 9 | ) 10 | 11 | type Client struct { 12 | ws *websocket.Conn 13 | start time.Time 14 | } 15 | 16 | func (client *Client) String() string { 17 | uptime := time.Now().UTC().Sub(client.start) 18 | var addr string 19 | if client.ws.Request().Header.Get("X-Real-Ip") != "" { 20 | addr = client.ws.Request().Header.Get("X-Real-Ip") 21 | } else { 22 | addr = client.ws.Request().RemoteAddr 23 | } 24 | return fmt.Sprintf("%s has been up for %s", addr, uptime) 25 | } 26 | 27 | func (client *Client) Send(cmd string) { 28 | msg := new(WsSendMessage) 29 | msg.MsgType = "log" 30 | 31 | m := make(map[string]string) 32 | m["log"] = cmd 33 | msg.Data, _ = json.Marshal(m) 34 | 35 | client.SendNew(msg) 36 | } 37 | 38 | func (client *Client) SendNew(msg *WsSendMessage) { 39 | err := websocket.JSON.Send(client.ws, msg) 40 | if err != nil { 41 | fmt.Println("error while Writing:", err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Luca Cervasio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /cli/connection.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/net/websocket" 6 | "os" 7 | // "strings" 8 | "encoding/json" 9 | "github.com/sdir/mosesacs/daemon" 10 | ) 11 | 12 | type Connection struct { 13 | ws *websocket.Conn 14 | Status string 15 | url string 16 | Incoming chan daemon.WsSendMessage 17 | } 18 | 19 | func (conn *Connection) Start(url string) { 20 | conn.url = url 21 | conn.Incoming = make(chan daemon.WsSendMessage) 22 | 23 | origin := "http://localhost/" 24 | ws, err := websocket.Dial(url, "", origin) 25 | if err != nil { 26 | fmt.Println("Error connecting to remote MosesACS instance") 27 | line.Close() 28 | os.Exit(1) 29 | } 30 | 31 | conn.ws = ws 32 | go conn.read() 33 | } 34 | 35 | func (conn *Connection) read() { 36 | for { 37 | var msg daemon.WsSendMessage 38 | err := websocket.JSON.Receive(conn.ws, &msg) 39 | if err != nil { 40 | fmt.Println("error while Reading:", err) 41 | // conn.Incoming <- "quit" 42 | break 43 | } 44 | 45 | if msg.MsgType == "ping" { 46 | conn.Write("pong") 47 | } else { 48 | conn.Incoming <- msg 49 | } 50 | } 51 | } 52 | 53 | func (conn *Connection) Close() { 54 | conn.ws.Close() 55 | } 56 | 57 | func (conn *Connection) Write(cmd string) { 58 | msg := new(daemon.WsSendMessage) 59 | msg.MsgType = "command" 60 | 61 | var temp = make(map[string]string) 62 | temp["command"] = cmd 63 | msg.Data, _ = json.Marshal(temp) 64 | 65 | err := websocket.JSON.Send(conn.ws, msg) 66 | if err != nil { 67 | fmt.Println("error while Writing:", err) 68 | // conn.Incoming <- "quit" 69 | } 70 | } 71 | 72 | func (conn *Connection) SendSyncCommand(cmd string) *daemon.WsSendMessage { 73 | ch := make(chan *daemon.WsSendMessage) 74 | conn.Write(cmd) 75 | m := <-ch 76 | 77 | return m 78 | } 79 | -------------------------------------------------------------------------------- /xmpp/xmpp.go: -------------------------------------------------------------------------------- 1 | package xmpp 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/xml" 6 | "github.com/tsuibin/goxmpp2/xmpp" 7 | // "flag" 8 | "fmt" 9 | "log" 10 | //"os" 11 | "strings" 12 | ) 13 | 14 | var c *xmpp.Client 15 | 16 | func StartClient(user string, pwd string, cb func(string)) { 17 | /* 18 | jidStr := flag.String("jid", "", "JID to log in as") 19 | pw := flag.String("pw", "", "password") 20 | flag.Parse() 21 | jid := xmpp.JID(*jidStr) 22 | if jid.Domain() == "" || *pw == "" { 23 | flag.Usage() 24 | os.Exit(2) 25 | } 26 | */ 27 | 28 | jid := xmpp.JID(user) 29 | 30 | stat := make(chan xmpp.Status) 31 | go func() { 32 | for s := range stat { 33 | log.Printf("connection status %d", s) 34 | } 35 | }() 36 | tlsConf := tls.Config{InsecureSkipVerify: true} 37 | var err error 38 | c, err = xmpp.NewClient(&jid, pwd, tlsConf, nil, xmpp.Presence{}, stat) 39 | if err != nil { 40 | log.Fatalf("NewClient(%v): %v", jid, err) 41 | } 42 | //defer c.Close() 43 | 44 | go func(ch <-chan xmpp.Stanza) { 45 | for obj := range ch { 46 | fmt.Printf("s: %v\n", obj) 47 | str := fmt.Sprintf("%v", obj) 48 | cb(str) 49 | } 50 | fmt.Println("done reading") 51 | }(c.Recv) 52 | 53 | /* 54 | roster := c.Roster.Get() 55 | fmt.Printf("%d roster entries:\n", len(roster)) 56 | for i, entry := range roster { 57 | fmt.Printf("%d: %v\n", i, entry) 58 | } 59 | */ 60 | 61 | /* 62 | p := make([]byte, 1024) 63 | for { 64 | nr, _ := os.Stdin.Read(p) 65 | if nr == 0 { 66 | break 67 | } 68 | s := string(p) 69 | dec := xml.NewDecoder(strings.NewReader(s)) 70 | t, err := dec.Token() 71 | if err != nil { 72 | fmt.Printf("token: %s\n", err) 73 | break 74 | } 75 | var se *xml.StartElement 76 | var ok bool 77 | if se, ok = t.(*xml.StartElement); !ok { 78 | fmt.Println("Couldn't find start element") 79 | break 80 | } 81 | var stan xmpp.Stanza 82 | switch se.Name.Local { 83 | case "iq": 84 | stan = &xmpp.Iq{} 85 | case "message": 86 | stan = &xmpp.Message{} 87 | case "presence": 88 | stan = &xmpp.Presence{} 89 | default: 90 | fmt.Println("Can't parse non-stanza.") 91 | continue 92 | } 93 | err = dec.Decode(stan) 94 | if err == nil { 95 | c.Send <- stan 96 | } else { 97 | fmt.Printf("Parse error: %v\n", err) 98 | break 99 | } 100 | } 101 | fmt.Println("done sending") 102 | */ 103 | } 104 | 105 | func SendConnectionRequest(cpe, username, password, from string) { 106 | outmsg := `` + username + `` + password + `` 107 | dec := xml.NewDecoder(strings.NewReader(outmsg)) 108 | var stan xmpp.Stanza 109 | stan = &xmpp.Iq{} 110 | err := dec.Decode(stan) 111 | if err == nil { 112 | fmt.Println(stan) 113 | c.Send <- stan 114 | } else { 115 | fmt.Printf("Parse error: %v\n", err) 116 | } 117 | 118 | } 119 | 120 | func Close() { 121 | fmt.Println("closing xmpp client") 122 | c.Close() 123 | } 124 | -------------------------------------------------------------------------------- /daemon/http_client_digest.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "fmt" 5 | // "io/ioutil" 6 | "crypto/md5" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | ) 14 | 15 | type myjar struct { 16 | jar map[string][]*http.Cookie 17 | } 18 | 19 | func (p *myjar) SetCookies(u *url.URL, cookies []*http.Cookie) { 20 | p.jar[u.Host] = cookies 21 | } 22 | 23 | func (p *myjar) Cookies(u *url.URL) []*http.Cookie { 24 | return p.jar[u.Host] 25 | } 26 | 27 | func Auth(username string, password string, uri string) (bool, error) { 28 | client := &http.Client{} 29 | jar := &myjar{} 30 | jar.jar = make(map[string][]*http.Cookie) 31 | client.Jar = jar 32 | var req *http.Request 33 | var resp *http.Response 34 | var err error 35 | req, err = http.NewRequest("GET", uri, nil) 36 | resp, err = client.Do(req) 37 | if err != nil { 38 | return false, err 39 | } 40 | if resp.StatusCode == 401 { 41 | var authorization map[string]string = DigestAuthParams(resp) 42 | realmHeader := authorization["realm"] 43 | qopHeader := authorization["qop"] 44 | nonceHeader := authorization["nonce"] 45 | opaqueHeader := authorization["opaque"] 46 | realm := realmHeader 47 | // A1 48 | h := md5.New() 49 | A1 := fmt.Sprintf("%s:%s:%s", username, realm, password) 50 | io.WriteString(h, A1) 51 | HA1 := fmt.Sprintf("%x", h.Sum(nil)) 52 | 53 | // A2 54 | h = md5.New() 55 | A2 := fmt.Sprintf("GET:%s", "/auth") 56 | io.WriteString(h, A2) 57 | HA2 := fmt.Sprintf("%x", h.Sum(nil)) 58 | 59 | // response 60 | cnonce := RandomKey() 61 | response := H(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")) 62 | 63 | // now make header 64 | AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s", opaque="%s", algorithm=MD5`, 65 | username, realmHeader, nonceHeader, "/auth", cnonce, qopHeader, response, opaqueHeader) 66 | req.Header.Set("Authorization", AuthHeader) 67 | resp, err = client.Do(req) 68 | } else { 69 | return false, fmt.Errorf("response status code should have been 401, it was %v", resp.StatusCode) 70 | } 71 | return resp.StatusCode == 200, err 72 | } 73 | 74 | /* 75 | Parse Authorization header from the http.Request. Returns a map of 76 | auth parameters or nil if the header is not a valid parsable Digest 77 | auth header. 78 | */ 79 | func DigestAuthParams(r *http.Response) map[string]string { 80 | s := strings.SplitN(r.Header.Get("Www-Authenticate"), " ", 2) 81 | if len(s) != 2 || s[0] != "Digest" { 82 | return nil 83 | } 84 | 85 | result := map[string]string{} 86 | for _, kv := range strings.Split(s[1], ",") { 87 | parts := strings.SplitN(kv, "=", 2) 88 | if len(parts) != 2 { 89 | continue 90 | } 91 | result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ") 92 | } 93 | return result 94 | } 95 | func RandomKey() string { 96 | k := make([]byte, 12) 97 | for bytes := 0; bytes < len(k); { 98 | n, err := rand.Read(k[bytes:]) 99 | if err != nil { 100 | panic("rand.Read() failed") 101 | } 102 | bytes += n 103 | } 104 | return base64.StdEncoding.EncodeToString(k) 105 | } 106 | 107 | /* 108 | H function for MD5 algorithm (returns a lower-case hex MD5 digest) 109 | */ 110 | func H(data string) string { 111 | digest := md5.New() 112 | digest.Write([]byte(data)) 113 | return fmt.Sprintf("%x", digest.Sum(nil)) 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moses ACS [![Build Status](https://travis-ci.org/lucacervasio/mosesacs.svg?branch=master)](https://travis-ci.org/lucacervasio/mosesacs) 2 | 3 | An ACS in Go for provisioning CPEs, suitable for test purposes or production deployment. 4 | 5 | ## Getting started 6 | 7 | Install the package: 8 | 9 | go get github.com/lucacervasio/mosesacs 10 | 11 | Run daemon: 12 | 13 | mosesacs -d 14 | 15 | Connect to it and get a cli: 16 | 17 | mosesacs 18 | 19 | Congratulations, you've connected to the daemon via websocket. Now you can issue commands via CLI or browse the embedded webserver at http://localhost:9292/www 20 | 21 | ## Compatibility on ARM 22 | 23 | Moses is built on purpose only with dependencies in pure GO. So it runs on ARM processors with no issues. We tested it on QNAP devices and Raspberry for remote control. 24 | 25 | ## CLI commands 26 | 27 | ### 1. `list`: list CPEs 28 | 29 | example: 30 | 31 | ``` 32 | moses@localhost:9292/> list 33 | cpe list 34 | CPE A54FD with OUI 006754 35 | ``` 36 | 37 | ### 2. `readMib SERIAL LEAF/SUBTREE`: read a specific leaf or a subtree 38 | 39 | example: 40 | 41 | ``` 42 | moses@localhost:9292/> readMib A54FD Device. 43 | Received an Inform from [::1]:58582 (3191 bytes) with SerialNumber A54FD and EventCodes 6 CONNECTION REQUEST 44 | InternetGatewayDevice.Time.NTPServer1 : pool.ntp.org 45 | InternetGatewayDevice.Time.CurrentLocalTime : 2014-07-11T09:08:25 46 | InternetGatewayDevice.Time.LocalTimeZone : +00:00 47 | InternetGatewayDevice.Time.LocalTimeZoneName : Greenwich Mean Time : Dublin 48 | InternetGatewayDevice.Time.DaylightSavingsUsed : 0 49 | ``` 50 | 51 | ### 3. `writeMib SERIAL LEAF VALUE`: issue a SetParameterValues and write a value into a leaf 52 | 53 | example: 54 | 55 | ``` 56 | moses@localhost:9292/> writeMib A54FD InternetGatewayDevice.Time.Enable false 57 | Received an Inform from [::1]:58582 (3191 bytes) with SerialNumber A54FD and EventCodes 6 CONNECTION REQUEST 58 | ``` 59 | 60 | ### 4. `GetParameterNames SERIAL LEAF/SUBTREE`: issue a GetParameterNames and get all leaves/objects at first level 61 | 62 | example: 63 | 64 | ``` 65 | moses@localhost:9292/> GetParameterNames A54FD InternetGatewayDevice. 66 | Received an Inform from [::1]:55385 (3119 bytes) with SerialNumber A54FD and EventCodes 6 CONNECTION REQUEST 67 | InternetGatewayDevice.LANDeviceNumberOfEntries : 0 68 | InternetGatewayDevice.WANDeviceNumberOfEntries : 0 69 | InternetGatewayDevice.DeviceInfo. : 0 70 | InternetGatewayDevice.ManagementServer. : 0 71 | InternetGatewayDevice.Time. : 0 72 | InternetGatewayDevice.Layer3Forwarding. : 0 73 | InternetGatewayDevice.LANDevice. : 0 74 | InternetGatewayDevice.WANDevice. : 0 75 | InternetGatewayDevice.X_00507F_InternetAcc. : 0 76 | InternetGatewayDevice.X_00507F_LAN. : 0 77 | InternetGatewayDevice.X_00507F_NAT. : 0 78 | InternetGatewayDevice.X_00507F_VLAN. : 0 79 | InternetGatewayDevice.X_00507F_Firewall. : 0 80 | InternetGatewayDevice.X_00507F_Applications. : 0 81 | InternetGatewayDevice.X_00507F_System. : 0 82 | InternetGatewayDevice.X_00507F_Status. : 0 83 | InternetGatewayDevice.X_00507F_Diagnostics. : 0 84 | ``` 85 | 86 | 87 | 88 | 89 | ## Services exposed 90 | 91 | Moses exposes three services: 92 | 93 | - http://localhost:9292/acs is the endpoint for the CPEs to connect 94 | - http://localhost:9292/www is the embedded webserver to control your CPEs 95 | - ws://localhost:9292/ws is the websocket endpoint used by the cli to issue commands. Read about the API specification if you want to build a custom frontend which interacts with mosesacs daemon. 96 | 97 | 98 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package cwmpclient 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "fmt" 7 | "github.com/lucacervasio/mosesacs/cwmp" 8 | "github.com/lucacervasio/mosesacs/daemon" 9 | "github.com/lucacervasio/mosesacs/xmpp" 10 | "io/ioutil" 11 | "log" 12 | "math/rand" 13 | "net/http" 14 | "net/http/cookiejar" 15 | "net/url" 16 | "strconv" 17 | "time" 18 | ) 19 | 20 | type Agent struct { 21 | Status string 22 | AcsUrl string 23 | Cpe daemon.CPE 24 | } 25 | 26 | func NewClient() (agent Agent) { 27 | serial := strconv.Itoa(random(1000, 5000)) 28 | connection_request_url := "/ConnectionRequest-" + serial 29 | cpe := daemon.CPE{serial, "MOONAR LABS", "001309", connection_request_url, "asd", "asd", "0 BOOTSTRAP", nil, &daemon.Request{}, "4324asd", time.Now().UTC(), "TR181", false} 30 | agent = Agent{"initializing", "http://localhost:9292/acs", cpe} 31 | log.Println(agent) 32 | return 33 | } 34 | 35 | func (a Agent) String() string { 36 | return fmt.Sprintf("Agent running with serial %s and connection request url %s\n", a.Cpe.SerialNumber, a.Cpe.ConnectionRequestURL) 37 | } 38 | 39 | func (a Agent) Run() { 40 | http.HandleFunc(a.Cpe.ConnectionRequestURL, a.connectionRequestHandler) 41 | log.Println("Start http server waiting connection request") 42 | a.startConnection() 43 | // a.startXmppConnection() 44 | 45 | http.ListenAndServe(":7547", nil) 46 | } 47 | 48 | func (a Agent) connectionRequestHandler(w http.ResponseWriter, r *http.Request) { 49 | log.Printf("got connection request, send Inform to %s", a.AcsUrl) 50 | a.startConnection() 51 | } 52 | 53 | func random(min, max int) int { 54 | rand.Seed(int64(time.Now().Nanosecond())) 55 | return rand.Intn(max-min) + min 56 | } 57 | 58 | func (a Agent) startXmppConnection() { 59 | log.Println("starting StartXmppConnection") 60 | xmpp.StartClient("cpe2@mosesacs.org", "password1234", func(str string) { 61 | log.Println("got " + str) 62 | }) 63 | } 64 | 65 | func (a Agent) startConnection() { 66 | log.Printf("send Inform to %s", a.AcsUrl) 67 | var msgToSend []byte 68 | msgToSend = []byte(cwmp.Inform(a.Cpe.SerialNumber)) 69 | 70 | tr := &http.Transport{} 71 | jar, _ := cookiejar.New(nil) 72 | client := &http.Client{Transport: tr, Jar: jar} 73 | envelope := cwmp.SoapEnvelope{} 74 | u, _ := url.Parse(a.AcsUrl) 75 | 76 | resp, err := client.Post(a.AcsUrl, "text/xml", bytes.NewBuffer(msgToSend)) 77 | if err != nil { 78 | log.Fatal("server unavailable") 79 | } 80 | log.Println(resp.Header) 81 | for { 82 | if resp.ContentLength == 0 { 83 | log.Println("got empty post, close connection") 84 | resp.Body.Close() 85 | tr.CloseIdleConnections() 86 | break 87 | } else { 88 | tmp, _ := ioutil.ReadAll(resp.Body) 89 | body := string(tmp) 90 | xml.Unmarshal(tmp, &envelope) 91 | 92 | if envelope.KindOf() == "GetParameterValues" { 93 | log.Println("Send GetParameterValuesResponse") 94 | var leaves cwmp.GetParameterValues_ 95 | xml.Unmarshal([]byte(body), &leaves) 96 | msgToSend = []byte(cwmp.BuildGetParameterValuesResponse(a.Cpe.SerialNumber, leaves)) 97 | } else if envelope.KindOf() == "GetParameterNames" { 98 | log.Println("Send GetParameterNamesResponse") 99 | var leaves cwmp.GetParameterNames_ 100 | xml.Unmarshal([]byte(body), &leaves) 101 | msgToSend = []byte(cwmp.BuildGetParameterNamesResponse(a.Cpe.SerialNumber, leaves)) 102 | } else { 103 | log.Println("send empty post") 104 | msgToSend = []byte("") 105 | } 106 | 107 | client.Jar.SetCookies(u, resp.Cookies()) 108 | resp, _ = client.Post(a.AcsUrl, "text/xml", bytes.NewBuffer(msgToSend)) 109 | log.Println(resp.Header) 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /cli/cli.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | // "encoding/xml" 6 | "fmt" 7 | 8 | "github.com/lucacervasio/liner" 9 | // "github.com/lucacervasio/mosesacs/cwmp" 10 | "os" 11 | "os/signal" 12 | "strings" 13 | 14 | "github.com/sdir/mosesacs/cwmp" 15 | "github.com/sdir/mosesacs/daemon" 16 | ) 17 | 18 | var line *liner.State 19 | var client Connection 20 | var context string 21 | 22 | func Run(url string) { 23 | line = liner.NewLiner() 24 | defer line.Close() 25 | 26 | client.Start(fmt.Sprintf("ws://%s/api", url)) 27 | defer client.Close() 28 | 29 | fmt.Printf("Connected to MosesACS @ws://%s/api\n", url) 30 | 31 | c := make(chan os.Signal, 1) 32 | signal.Notify(c, os.Interrupt) 33 | go func() { 34 | for sig := range c { 35 | // sig is a ^C, handle it 36 | if sig.String() == "interrupt" { 37 | fmt.Printf("\n") 38 | quit(url, line) 39 | } 40 | } 41 | }() 42 | 43 | baseCmds := []string{"exit", "setxmpp", "help", "version", "list", "status", "shutdown", "uptime", "readMib", "writeMib", "GetParameterNames", "set"} 44 | contextCmds := []string{"summary"} 45 | 46 | line.SetCompleter(func(line string) (c []string) { 47 | if strings.HasPrefix(line, "cpe ") { 48 | // should return the list of cpes as second argument 49 | arr := strings.Split(line, " ") 50 | 51 | cpes := []string{"cpe1", "cpe12", "cpe3"} // TODO get cpe list via ws 52 | for _, n := range cpes { 53 | if strings.HasPrefix(n, strings.ToLower(arr[1])) { 54 | c = append(c, arr[0]+" "+n) 55 | } 56 | } 57 | } else { 58 | // cycle through all available commands 59 | var cmds []string 60 | if context != "" { 61 | // if in context cycle through cpe-specific commands 62 | cmds = contextCmds 63 | } else { 64 | // otherwise cycle though base commands 65 | cmds = baseCmds 66 | } 67 | 68 | for _, n := range cmds { 69 | if strings.HasPrefix(n, strings.ToLower(line)) { 70 | c = append(c, n) 71 | } 72 | } 73 | } 74 | return 75 | }) 76 | 77 | if f, err := os.Open(fmt.Sprintf(os.ExpandEnv("$HOME")+"/.moses@%s.history", url)); err == nil { 78 | line.ReadHistory(f) 79 | f.Close() 80 | } 81 | 82 | go receiver() 83 | context = "" 84 | 85 | for { 86 | if cmd, err := line.Prompt(fmt.Sprintf("moses@%s/%s> ", url, context)); err != nil { 87 | fmt.Println("Error reading line: ", err) 88 | } else { 89 | // add to history 90 | if cmd == "exit" { 91 | quit(url, line) 92 | } else if cmd != "" && cmd != "\n" && cmd != "\r\n" { 93 | line.AppendHistory(cmd) 94 | processCommand(cmd) 95 | } 96 | } 97 | } 98 | 99 | // quit 100 | quit(url, line) 101 | } 102 | 103 | func receiver() { 104 | for { 105 | msg := <-client.Incoming 106 | if msg.MsgType == "quit" { 107 | quit("TODO", line) 108 | } 109 | 110 | switch msg.MsgType { 111 | case "cpes": 112 | cpes := new(daemon.MsgCPEs) 113 | err := json.Unmarshal(msg.Data, &cpes) 114 | if err != nil { 115 | fmt.Println("error:", err) 116 | } 117 | 118 | line.PrintAbovePrompt("cpe list") 119 | for key, value := range cpes.CPES { 120 | line.PrintAbovePrompt(fmt.Sprintf("CPE %s with OUI %s", key, value.OUI)) 121 | } 122 | case "GetParameterNamesResponse": 123 | getParameterNames := new(cwmp.GetParameterNamesResponse) 124 | err := json.Unmarshal(msg.Data, &getParameterNames) 125 | if err != nil { 126 | fmt.Println("error:", err) 127 | } 128 | // fmt.Println(getParameterNames.ParameterList) 129 | for idx := range getParameterNames.ParameterList { 130 | line.PrintAbovePrompt(fmt.Sprintf("%s : %s", getParameterNames.ParameterList[idx].Name, getParameterNames.ParameterList[idx].Writable)) 131 | } 132 | case "GetParameterValuesResponse": 133 | getParameterValues := new(cwmp.GetParameterValuesResponse) 134 | err := json.Unmarshal(msg.Data, &getParameterValues) 135 | if err != nil { 136 | fmt.Println("error:", err) 137 | } 138 | for idx := range getParameterValues.ParameterList { 139 | line.PrintAbovePrompt(fmt.Sprintf("%s : %s", getParameterValues.ParameterList[idx].Name, getParameterValues.ParameterList[idx].Value)) 140 | } 141 | case "SetParameterValuesResponse": 142 | line.PrintAbovePrompt(fmt.Sprintf("got SetParameterValuesResponse")) 143 | case "log": 144 | log := make(map[string]string) 145 | err := json.Unmarshal(msg.Data, &log) 146 | if err != nil { 147 | fmt.Println("error:", err) 148 | } 149 | 150 | // fmt.Printf("%+v",log["prova"]) 151 | if log["log"] == "ping" { 152 | // received ping from daemon 153 | } else { 154 | line.PrintAbovePrompt(fmt.Sprintf("%s", log["log"])) 155 | } 156 | 157 | } 158 | 159 | // fmt.Println(msg) 160 | /* 161 | var e cwmp.SoapEnvelope 162 | xml.Unmarshal([]byte(msg), &e) 163 | 164 | if e.KindOf() == "GetParameterValuesResponse" { 165 | var envelope cwmp.GetParameterValuesResponse 166 | xml.Unmarshal([]byte(msg), &envelope) 167 | 168 | for idx := range envelope.ParameterList { 169 | line.PrintAbovePrompt(string(fmt.Sprintf("%s : %s", envelope.ParameterList[idx].Name, envelope.ParameterList[idx].Value))) 170 | } 171 | 172 | } else if e.KindOf() == "GetParameterNamesResponse" { 173 | line.PrintAbovePrompt(string(msg)) 174 | 175 | var envelope cwmp.GetParameterNamesResponse 176 | xml.Unmarshal([]byte(msg), &envelope) 177 | 178 | for idx := range envelope.ParameterList { 179 | line.PrintAbovePrompt(string(fmt.Sprintf("%s : %s", envelope.ParameterList[idx].Name, envelope.ParameterList[idx].Writable))) 180 | } 181 | 182 | } else { 183 | line.PrintAbovePrompt(string(msg)) 184 | 185 | } 186 | 187 | */ 188 | // line.PrintAbovePrompt(msg.MsgType) 189 | } 190 | } 191 | 192 | func quit(url string, line *liner.State) { 193 | if f, err := os.Create(fmt.Sprintf(os.ExpandEnv("$HOME")+"/.moses@%s.history", url)); err != nil { 194 | fmt.Println("Error writing history file: ", err) 195 | } else { 196 | line.WriteHistory(f) 197 | f.Close() 198 | } 199 | 200 | line.Close() 201 | fmt.Println("Disconnected. Bye.") 202 | os.Exit(0) 203 | } 204 | 205 | func processCommand(cmd string) { 206 | switch { 207 | case strings.HasPrefix(cmd, "cpe "): 208 | arr := strings.Split(cmd, " ") 209 | 210 | context = arr[1] 211 | case strings.Contains(cmd, "version"): 212 | client.Write("version") 213 | case strings.Contains(cmd, "readMib"): 214 | client.Write(cmd) 215 | case strings.Contains(cmd, "writeMib"): 216 | client.Write(cmd) 217 | // case strings.Contains(cmd, "changeDuState"): 218 | // client.Write(cmd) 219 | case strings.Contains(cmd, "GetParameterNames"): 220 | client.Write(cmd) 221 | case strings.Contains(cmd, "list"): 222 | client.Write("list") 223 | case strings.Contains(cmd, "status"): 224 | client.Write("status") 225 | case strings.Contains(cmd, "setxmpp"): 226 | client.Write(cmd) 227 | default: 228 | fmt.Println("Unknown command") 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /daemon/daemon.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "time" 12 | 13 | "github.com/oleiade/lane" 14 | "github.com/sdir/mosesacs/cwmp" 15 | "github.com/sdir/mosesacs/www" 16 | "github.com/sdir/mosesacs/xmpp" 17 | "golang.org/x/net/websocket" 18 | ) 19 | 20 | const Version = "0.2.6" 21 | 22 | var logger MosesWriter 23 | var xmppGlobalUser string 24 | 25 | type MosesWriter interface { 26 | Logger(string) 27 | } 28 | 29 | type BasicWriter struct { 30 | } 31 | 32 | func (w *BasicWriter) Logger(log string) { 33 | fmt.Println("Free:", log) 34 | } 35 | 36 | type Request struct { 37 | Id string 38 | Websocket *websocket.Conn 39 | CwmpMessage string 40 | Callback func(msg *WsSendMessage) error 41 | } 42 | 43 | type CPE struct { 44 | SerialNumber string 45 | Manufacturer string 46 | OUI string 47 | ConnectionRequestURL string 48 | XmppId string 49 | XmppUsername string 50 | XmppPassword string 51 | SoftwareVersion string 52 | ExternalIPAddress string 53 | State string 54 | Queue *lane.Queue 55 | Waiting *Request 56 | HardwareVersion string 57 | LastConnection time.Time 58 | DataModel string 59 | KeepConnectionOpen bool 60 | } 61 | 62 | type Message struct { 63 | SerialNumber string 64 | Message string 65 | } 66 | 67 | type WsMessage struct { 68 | Cmd string 69 | } 70 | 71 | type WsSendMessage struct { 72 | MsgType string 73 | Data json.RawMessage 74 | } 75 | 76 | type MsgCPEs struct { 77 | CPES map[string]CPE 78 | } 79 | 80 | var cpes map[string]CPE // by serial 81 | var sessions map[string]*CPE // by session cookie 82 | var clients []Client 83 | 84 | func CwmpHandler(w http.ResponseWriter, r *http.Request) { 85 | // log.Printf("New connection coming from %s", r.RemoteAddr) 86 | defer r.Body.Close() 87 | tmp, _ := ioutil.ReadAll(r.Body) 88 | 89 | body := string(tmp) 90 | len := len(body) 91 | 92 | log.Printf("body: %v", body) 93 | // log.Printf("body length: %v", len) 94 | 95 | var envelope cwmp.SoapEnvelope 96 | xml.Unmarshal(tmp, &envelope) 97 | 98 | messageType := envelope.Body.CWMPMessage.XMLName.Local 99 | 100 | var cpe *CPE 101 | 102 | w.Header().Set("Server", "MosesACS "+Version) 103 | 104 | if messageType != "Inform" { 105 | if cookie, err := r.Cookie("mosesacs"); err == nil { 106 | cpe = sessions[cookie.Value] 107 | } else { 108 | fmt.Println("cookie 'mosesacs' missing") 109 | w.WriteHeader(401) 110 | return 111 | } 112 | } 113 | 114 | if messageType == "Inform" { 115 | var Inform cwmp.CWMPInform 116 | xml.Unmarshal(tmp, &Inform) 117 | 118 | var addr string 119 | if r.Header.Get("X-Real-Ip") != "" { 120 | addr = r.Header.Get("X-Real-Ip") 121 | } else { 122 | addr = r.RemoteAddr 123 | } 124 | 125 | if _, exists := cpes[Inform.DeviceId.SerialNumber]; !exists { 126 | fmt.Println("found ConnectionRequest " + Inform.GetConnectionRequest()) 127 | cpes[Inform.DeviceId.SerialNumber] = CPE{ 128 | SerialNumber: Inform.DeviceId.SerialNumber, 129 | LastConnection: time.Now().UTC(), 130 | SoftwareVersion: Inform.GetSoftwareVersion(), 131 | HardwareVersion: Inform.GetHardwareVersion(), 132 | ExternalIPAddress: addr, 133 | ConnectionRequestURL: Inform.GetConnectionRequest(), 134 | OUI: Inform.DeviceId.OUI, 135 | Queue: lane.NewQueue(), 136 | DataModel: Inform.GetDataModelType(), 137 | KeepConnectionOpen: false} 138 | } 139 | obj := cpes[Inform.DeviceId.SerialNumber] 140 | cpe := &obj 141 | cpe.LastConnection = time.Now().UTC() 142 | 143 | log.Printf("Received an Inform from %s (%d bytes) with SerialNumber %s and EventCodes %s", addr, len, Inform.DeviceId.SerialNumber, Inform.GetEvents()) 144 | log.Printf("Soap envelope has mustUnderstand %s\n", envelope.Header.Id) 145 | // logger.Logger("ciao") 146 | sendAll(fmt.Sprintf("Received an Inform from %s (%d bytes) with SerialNumber %s and EventCodes %s", addr, len, Inform.DeviceId.SerialNumber, Inform.GetEvents())) 147 | 148 | expiration := time.Now().AddDate(0, 0, 1) // expires in 1 day 149 | hash := "asdadasd" 150 | 151 | cookie := http.Cookie{Name: "mosesacs", Value: hash, Expires: expiration} 152 | http.SetCookie(w, &cookie) 153 | sessions[hash] = cpe 154 | 155 | fmt.Fprintf(w, cwmp.InformResponse(envelope.Header.Id)) 156 | } else if messageType == "TransferComplete" { 157 | 158 | } else if messageType == "GetRPC" { 159 | 160 | } else { 161 | // if messageType == "GetParameterValuesResponse" { 162 | // eseguo del parsing, invio i dati via websocket o altro 163 | 164 | // } else if len == 0 { 165 | if len == 0 { 166 | // empty post 167 | log.Printf("Got Empty Post") 168 | } 169 | 170 | if cpe.Waiting != nil { 171 | var e cwmp.SoapEnvelope 172 | xml.Unmarshal([]byte(body), &e) 173 | 174 | if e.KindOf() == "GetParameterNamesResponse" { 175 | var envelope cwmp.GetParameterNamesResponse 176 | xml.Unmarshal([]byte(body), &envelope) 177 | 178 | msg := new(WsSendMessage) 179 | msg.MsgType = "GetParameterNamesResponse" 180 | msg.Data, _ = json.Marshal(envelope) 181 | 182 | cpe.Waiting.Callback(msg) 183 | // if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { 184 | // fmt.Println("error while sending back answer:", err) 185 | // } 186 | 187 | } else if e.KindOf() == "GetParameterValuesResponse" { 188 | var envelope cwmp.GetParameterValuesResponse 189 | xml.Unmarshal([]byte(body), &envelope) 190 | 191 | msg := new(WsSendMessage) 192 | msg.MsgType = "GetParameterValuesResponse" 193 | msg.Data, _ = json.Marshal(envelope) 194 | 195 | cpe.Waiting.Callback(msg) 196 | // if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { 197 | // fmt.Println("error while sending back answer:", err) 198 | // } 199 | 200 | } else if e.KindOf() == "SetParameterValuesResponse" { 201 | var envelope cwmp.SetParameterValuesResponse 202 | xml.Unmarshal([]byte(body), &envelope) 203 | 204 | msg := new(WsSendMessage) 205 | msg.MsgType = "log" 206 | var temp = make(map[string]string) 207 | temp["log"] = envelope.Status 208 | msg.Data, _ = json.Marshal(temp) 209 | 210 | cpe.Waiting.Callback(msg) 211 | 212 | } else { 213 | msg := new(WsMessage) 214 | msg.Cmd = body 215 | 216 | if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { 217 | fmt.Println("error while sending back answer:", err) 218 | } 219 | 220 | } 221 | 222 | cpe.Waiting = nil 223 | } 224 | 225 | // Got Empty Post or a Response. Now check for any event to send, otherwise 204 226 | if cpe.Queue.Size() > 0 { 227 | req := cpe.Queue.Dequeue().(Request) 228 | // fmt.Println("sending "+req.CwmpMessage) 229 | fmt.Fprintf(w, req.CwmpMessage) 230 | cpe.Waiting = &req 231 | } else { 232 | if cpe.KeepConnectionOpen { 233 | fmt.Println("I'm keeping connection open") 234 | } else { 235 | w.WriteHeader(204) 236 | } 237 | } 238 | } 239 | 240 | } 241 | 242 | func doConnectionRequest(SerialNumber string) { 243 | fmt.Println("issuing a connection request to CPE", SerialNumber) 244 | if cpes[SerialNumber].XmppId != "" { 245 | fmt.Println("cr via xmpp") 246 | xmpp.SendConnectionRequest(cpes[SerialNumber].XmppId, cpes[SerialNumber].XmppUsername, cpes[SerialNumber].XmppPassword, xmppGlobalUser) 247 | } else { 248 | fmt.Println("cr via http") 249 | Auth("user", "pass", cpes[SerialNumber].ConnectionRequestURL) 250 | } 251 | } 252 | 253 | func staticPage(w http.ResponseWriter, r *http.Request) { 254 | fmt.Fprint(w, www.Index) 255 | } 256 | 257 | func fontsPage(w http.ResponseWriter, r *http.Request) { 258 | http.ServeFile(w, r, "./www"+r.URL.Path) 259 | } 260 | 261 | func Run(port *int, logObj MosesWriter, xmppUser, xmppPass string) { 262 | xmppGlobalUser = xmppUser 263 | logger = logObj 264 | cpes = make(map[string]CPE) 265 | sessions = make(map[string]*CPE) 266 | 267 | // plain http handler for cpes 268 | fmt.Printf("HTTP Handler installed at http://0.0.0.0:%d/acs for cpes to connect\n", *port) 269 | http.HandleFunc("/acs", CwmpHandler) 270 | 271 | fmt.Printf("Websocket API endpoint installed at http://0.0.0.0:%d/api for admin stuff\n", *port) 272 | http.Handle("/api", websocket.Handler(websocketHandler)) 273 | 274 | fmt.Printf("WEB Handler installed at http://0.0.0.0:%d/www\n", *port) 275 | http.HandleFunc("/www", staticPage) 276 | http.HandleFunc("/fonts/", fontsPage) 277 | 278 | if xmppUser != "" { 279 | // starting xmpp client 280 | xmpp.StartClient(xmppUser, xmppPass, func(str string) { 281 | log.Println(str) 282 | }) 283 | defer xmpp.Close() 284 | } 285 | 286 | err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil) 287 | if err != nil { 288 | log.Fatal(err) 289 | os.Exit(1) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /daemon/websocket_handler.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/sdir/mosesacs/cwmp" 12 | "golang.org/x/net/websocket" 13 | ) 14 | 15 | func websocketHandler(ws *websocket.Conn) { 16 | fmt.Println("New websocket client via ws") 17 | defer ws.Close() 18 | 19 | client := Client{ws: ws, start: time.Now().UTC()} 20 | clients = append(clients, client) 21 | // client.Read() 22 | 23 | quit := make(chan bool) 24 | go periodicWsChecker(&client, quit) 25 | 26 | for { 27 | var msg WsSendMessage 28 | err := websocket.JSON.Receive(ws, &msg) 29 | if err != nil { 30 | fmt.Println("error while Receive:", err) 31 | quit <- true 32 | break 33 | } 34 | 35 | data := make(map[string]interface{}) 36 | err = json.Unmarshal(msg.Data, &data) 37 | 38 | if err != nil { 39 | fmt.Println("error:", err) 40 | } 41 | 42 | m := data["command"].(string) 43 | 44 | if m == "list" { 45 | 46 | ms := new(WsSendMessage) 47 | ms.MsgType = "cpes" 48 | msgCpes := new(MsgCPEs) 49 | msgCpes.CPES = cpes 50 | ms.Data, _ = json.Marshal(msgCpes) 51 | 52 | client.SendNew(ms) 53 | 54 | // client requests a GetParametersValues to cpe with serial 55 | //serial := "1" 56 | //leaf := "Device.Time." 57 | // enqueue this command with the ws number to get the answer back 58 | 59 | } else if m == "version" { 60 | client.Send(fmt.Sprintf("MosesAcs Daemon %s", Version)) 61 | 62 | } else if m == "status" { 63 | var response string 64 | for i := range clients { 65 | response += clients[i].String() + "\n" 66 | } 67 | 68 | client.Send(response) 69 | } else if strings.Contains(m, "setxmpp") { 70 | i := strings.Split(m, " ") 71 | if _, exists := cpes[i[1]]; exists { 72 | c := cpes[i[1]] 73 | c.XmppId = i[2] 74 | if len(i) == 5 { 75 | c.XmppUsername = i[3] 76 | c.XmppPassword = i[4] 77 | } 78 | cpes[i[1]] = c 79 | } else { 80 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", i[1])) 81 | } 82 | } else if strings.Contains(m, "changeDuState") { 83 | i := strings.Split(m, " ") 84 | 85 | ops := data["ops"].([]interface{}) 86 | 87 | var operations []fmt.Stringer 88 | for _, obj := range ops { 89 | op := obj.(map[string]interface{}) 90 | fmt.Println(op) 91 | type_cmd := op["type"].(string) 92 | if type_cmd == "install" { 93 | install_op := &cwmp.InstallOpStruct{Url: op["url"].(string), Uuid: op["uuid"].(string), Username: op["username"].(string), Password: op["password"].(string), ExecutionEnvironment: op["environment"].(string)} 94 | operations = append(operations, install_op) 95 | } else if type_cmd == "update" { 96 | update_op := &cwmp.UpdateOpStruct{Url: op["url"].(string), Uuid: op["uuid"].(string), Username: op["username"].(string), Password: op["password"].(string), Version: op["version"].(string)} 97 | operations = append(operations, update_op) 98 | } else if type_cmd == "uninstall" { 99 | uninstall_op := &cwmp.UninstallOpStruct{Version: op["version"].(string), Uuid: op["uuid"].(string), ExecutionEnvironment: op["environment"].(string)} 100 | operations = append(operations, uninstall_op) 101 | } 102 | } 103 | 104 | req := Request{i[1], ws, cwmp.ChangeDuState(operations), func(msg *WsSendMessage) error { 105 | if err := websocket.JSON.Send(ws, msg); err != nil { 106 | fmt.Println("error while sending back answer:", err) 107 | } 108 | 109 | return err 110 | }} 111 | 112 | if _, exists := cpes[i[1]]; exists { 113 | cpes[i[1]].Queue.Enqueue(req) 114 | if cpes[i[1]].State != "Connected" { 115 | // issue a connection request 116 | go doConnectionRequest(i[1]) 117 | } 118 | } else { 119 | if err := websocket.JSON.Send(ws, map[string]string{"status": "error", "reason": fmt.Sprintf("CPE with serial %s not found", i[1])}); err != nil { 120 | fmt.Println("error while sending back answer:", err) 121 | } 122 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", i[1])) 123 | } 124 | } else if m == "download" { 125 | CpeSerial := data["serial"].(string) 126 | 127 | req := Request{CpeSerial, ws, cwmp.Download(data["filetype"].(string), data["url"].(string), data["username"].(string), data["password"].(string), data["filesize"].(string)), func(msg *WsSendMessage) error { 128 | if err := websocket.JSON.Send(ws, msg); err != nil { 129 | fmt.Println("error while sending back answer:", err) 130 | } 131 | 132 | return err 133 | }} 134 | 135 | if _, exists := cpes[CpeSerial]; exists { 136 | cpes[CpeSerial].Queue.Enqueue(req) 137 | if cpes[CpeSerial].State != "Connected" { 138 | // issue a connection request 139 | go doConnectionRequest(CpeSerial) 140 | } 141 | } else { 142 | if err := websocket.JSON.Send(ws, map[string]string{"status": "error", "reason": fmt.Sprintf("CPE with serial %s not found", CpeSerial)}); err != nil { 143 | fmt.Println("error while sending back answer:", err) 144 | } 145 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", CpeSerial)) 146 | } 147 | } else if strings.Contains(m, "canceltransfer") { 148 | CpeSerial := data["serial"].(string) 149 | 150 | req := Request{CpeSerial, ws, cwmp.CancelTransfer(), func(msg *WsSendMessage) error { 151 | if err := websocket.JSON.Send(ws, msg); err != nil { 152 | fmt.Println("error while sending back answer:", err) 153 | } 154 | 155 | return err 156 | }} 157 | 158 | if _, exists := cpes[CpeSerial]; exists { 159 | cpes[CpeSerial].Queue.Enqueue(req) 160 | if cpes[CpeSerial].State != "Connected" { 161 | // issue a connection request 162 | go doConnectionRequest(CpeSerial) 163 | } 164 | } else { 165 | if err := websocket.JSON.Send(ws, map[string]string{"status": "error", "reason": fmt.Sprintf("CPE with serial %s not found", CpeSerial)}); err != nil { 166 | fmt.Println("error while sending back answer:", err) 167 | } 168 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", CpeSerial)) 169 | } 170 | 171 | } else if strings.Contains(m, "scheduledownload") { 172 | CpeSerial := data["serial"].(string) 173 | 174 | w := data["windows"].([]interface{}) 175 | var windows []fmt.Stringer 176 | for _, obj := range w { 177 | i := obj.(map[string]interface{}) 178 | wdw := &cwmp.TimeWindowStruct{ 179 | WindowStart: i["windowstart"].(string), 180 | WindowEnd: i["windowend"].(string), 181 | WindowMode: i["windowmode"].(string), 182 | UserMessage: i["usermessage"].(string), 183 | MaxRetries: i["maxretries"].(string), 184 | } 185 | windows = append(windows, wdw) 186 | } 187 | 188 | req := Request{CpeSerial, ws, cwmp.ScheduleDownload(data["filetype"].(string), data["url"].(string), data["username"].(string), data["password"].(string), data["filesize"].(string), windows), func(msg *WsSendMessage) error { 189 | if err := websocket.JSON.Send(ws, msg); err != nil { 190 | fmt.Println("error while sending back answer:", err) 191 | } 192 | 193 | return err 194 | }} 195 | 196 | if _, exists := cpes[CpeSerial]; exists { 197 | cpes[CpeSerial].Queue.Enqueue(req) 198 | if cpes[CpeSerial].State != "Connected" { 199 | // issue a connection request 200 | go doConnectionRequest(CpeSerial) 201 | } 202 | } else { 203 | if err := websocket.JSON.Send(ws, map[string]string{"status": "error", "reason": fmt.Sprintf("CPE with serial %s not found", CpeSerial)}); err != nil { 204 | fmt.Println("error while sending back answer:", err) 205 | } 206 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", CpeSerial)) 207 | } 208 | } else if strings.Contains(m, "readMib") { 209 | i := strings.Split(m, " ") 210 | // cpeSerial, _ := strconv.Atoi(i[1]) 211 | // fmt.Printf("CPE %d\n", cpeSerial) 212 | // fmt.Printf("LEAF %s\n", i[2]) 213 | req := Request{i[1], ws, cwmp.GetParameterValues(i[2]), func(msg *WsSendMessage) error { 214 | if err := websocket.JSON.Send(ws, msg); err != nil { 215 | fmt.Println("error while sending back answer:", err) 216 | } 217 | 218 | return err 219 | }} 220 | 221 | if _, exists := cpes[i[1]]; exists { 222 | cpes[i[1]].Queue.Enqueue(req) 223 | if cpes[i[1]].State != "Connected" { 224 | // issue a connection request 225 | go doConnectionRequest(i[1]) 226 | } 227 | } else { 228 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", i[1])) 229 | } 230 | 231 | } else if strings.Contains(m, "writeMib") { 232 | i := strings.Split(m, " ") 233 | req := Request{i[1], ws, cwmp.SetParameterValues(i[2], i[3]), func(msg *WsSendMessage) error { 234 | if err := websocket.JSON.Send(ws, msg); err != nil { 235 | fmt.Println("error while sending back answer:", err) 236 | } 237 | 238 | return err 239 | }} 240 | 241 | if _, exists := cpes[i[1]]; exists { 242 | cpes[i[1]].Queue.Enqueue(req) 243 | if cpes[i[1]].State != "Connected" { 244 | // issue a connection request 245 | go doConnectionRequest(i[1]) 246 | } 247 | } else { 248 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", i[1])) 249 | } 250 | } else if strings.Contains(m, "GetParameterNames") { 251 | i := strings.Split(m, " ") 252 | nextlevel, _ := strconv.Atoi(i[3]) 253 | req := Request{i[1], ws, cwmp.GetParameterNames(i[2], nextlevel), func(msg *WsSendMessage) error { 254 | if err := websocket.JSON.Send(ws, msg); err != nil { 255 | fmt.Println("error while sending back answer:", err) 256 | } 257 | 258 | return err 259 | }} 260 | 261 | if _, exists := cpes[i[1]]; exists { 262 | cpes[i[1]].Queue.Enqueue(req) 263 | if cpes[i[1]].State != "Connected" { 264 | // issue a connection request 265 | go doConnectionRequest(i[1]) 266 | } 267 | } else { 268 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", i[1])) 269 | } 270 | } else if m == "GetParameterValues" { 271 | cpe := data["cpe"].(string) 272 | req := Request{cpe, ws, cwmp.GetParameterValues(data["object"].(string)), func(msg *WsSendMessage) error { 273 | if err := websocket.JSON.Send(ws, msg); err != nil { 274 | fmt.Println("error while sending back answer:", err) 275 | } 276 | 277 | return err 278 | }} 279 | if _, exists := cpes[cpe]; exists { 280 | cpes[cpe].Queue.Enqueue(req) 281 | if cpes[cpe].State != "Connected" { 282 | // issue a connection request 283 | go doConnectionRequest(cpe) 284 | } 285 | } else { 286 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", cpe)) 287 | } 288 | } else if m == "GetSummary" { 289 | cpe := data["cpe"].(string) 290 | ch := make(chan *WsSendMessage) 291 | 292 | // GetParameterNames per leggere la mib velocemente 293 | req := Request{cpe, ws, cwmp.GetParameterNames(data["object"].(string), 0), func(msg *WsSendMessage) error { 294 | fmt.Println("sono nella callback della GetParameterNames") 295 | ch <- msg 296 | return nil 297 | }} 298 | if _, exists := cpes[cpe]; exists { 299 | cpes[cpe].Queue.Enqueue(req) 300 | if cpes[cpe].State != "Connected" { 301 | // issue a connection request 302 | go doConnectionRequest(cpe) 303 | } 304 | } else { 305 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", cpe)) 306 | } 307 | fmt.Println("I'm suspended waiting for you to return the message") 308 | m := <-ch 309 | fmt.Println("is back") 310 | getParameterNames := new(cwmp.GetParameterNamesResponse) 311 | err := json.Unmarshal(m.Data, &getParameterNames) 312 | if err != nil { 313 | fmt.Println("error:", err) 314 | } 315 | 316 | objectsToCheck := map[string][]string{} 317 | re_wan_ip := regexp.MustCompile(`InternetGatewayDevice.WANDevice.(\d+).WANConnectionDevice.(\d+).WANIPConnection.(\d+).(Name|ExternalIPAddress|Enable|NATEnabled|Username|ConnectionTrigger|AddressingType|DefaultGateway|ConnectionType|ConnectionStatus)`) 318 | re_wan_ppp := regexp.MustCompile(`InternetGatewayDevice.WANDevice.(\d+).WANConnectionDevice.(\d+).WANPPPConnection.(\d+).(Name|ExternalIPAddress|Enable|ConnectionTrigger|AddressingType|DefaultGateway|ConnectionType|ConnectionStatus)`) 319 | re_hosts := regexp.MustCompile(`InternetGatewayDevice.LANDevice.1.Hosts.Host.(\d+).(Active|HostName|IPAddress|MACAddress|InterfaceType)`) 320 | re_wifi := regexp.MustCompile(`InternetGatewayDevice.LANDevice.1.WLANConfiguration.(\d+).(SSID|Enable|Status)`) 321 | re_voice := regexp.MustCompile(`InternetGatewayDevice.Services.VoiceService.(\d+).VoiceProfile.(\d+).Line.(\d+).(SIP.AuthUserName|SIP.URI|Enable|Status)`) 322 | // parso la GetParameterNamesResponse per creare la GetParameterValues multipla con le sole foglie che interessano il summary 323 | for idx := range getParameterNames.ParameterList { 324 | // looking for WAN IPConnection 325 | // InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.2.DefaultGateway 326 | match := re_wan_ip.FindStringSubmatch(getParameterNames.ParameterList[idx].Name) 327 | if len(match) != 0 { 328 | objectsToCheck["WAN device "+match[1]+" connection "+match[2]+" IP connection "+match[3]] = append(objectsToCheck["WAN device "+match[1]+" connection "+match[2]+" IP connection "+match[3]], "InternetGatewayDevice.WANDevice."+match[1]+".WANConnectionDevice."+match[2]+".WANIPConnection."+match[3]+"."+match[4]) 329 | } 330 | 331 | // looking for WAN PPPConnection 332 | match = re_wan_ppp.FindStringSubmatch(getParameterNames.ParameterList[idx].Name) 333 | if len(match) != 0 { 334 | objectsToCheck["WAN device "+match[1]+" connection "+match[2]+" PPP connection "+match[3]] = append(objectsToCheck["WAN device "+match[1]+" connection "+match[2]+" PPP connection "+match[3]], "InternetGatewayDevice.WANDevice."+match[1]+".WANConnectionDevice."+match[2]+".WANPPPConnection."+match[3]+"."+match[4]) 335 | } 336 | 337 | // looking for LAN 338 | match = re_hosts.FindStringSubmatch(getParameterNames.ParameterList[idx].Name) 339 | if len(match) != 0 { 340 | objectsToCheck["HOST"+match[1]] = append(objectsToCheck["HOST"+match[1]], "InternetGatewayDevice.LANDevice.1.Hosts.Host."+match[1]+"."+match[2]) 341 | } 342 | // looking for WIFI 343 | match = re_wifi.FindStringSubmatch(getParameterNames.ParameterList[idx].Name) 344 | if len(match) != 0 { 345 | objectsToCheck["WIFI"+match[1]] = append(objectsToCheck["WIFI"+match[1]], "InternetGatewayDevice.LANDevice.1.WLANConfiguration."+match[1]+"."+match[2]) 346 | } 347 | // looking for VOICE 348 | match = re_voice.FindStringSubmatch(getParameterNames.ParameterList[idx].Name) 349 | if len(match) != 0 { 350 | objectsToCheck["VOICE "+match[1]+" profile "+match[2]+" line "+match[3]] = append(objectsToCheck["VOICE "+match[1]+" profile "+match[2]+" line "+match[3]], "InternetGatewayDevice.Services.VoiceService."+match[1]+".VoiceProfile."+match[2]+".Line."+match[3]+"."+match[4]) 351 | } 352 | 353 | } 354 | 355 | // GetParameterMultiValues 356 | leaves := []string{} 357 | for idx := range objectsToCheck { 358 | for i := range objectsToCheck[idx] { 359 | leaves = append(leaves, objectsToCheck[idx][i]) 360 | } 361 | } 362 | req = Request{cpe, ws, cwmp.GetParameterMultiValues(leaves), func(msg *WsSendMessage) error { 363 | fmt.Println("sono nella callback") 364 | ch <- msg 365 | return nil // TODO da implementare un timeout ? boh 366 | }} 367 | if _, exists := cpes[cpe]; exists { 368 | cpes[cpe].Queue.Enqueue(req) 369 | if cpes[cpe].State != "Connected" { 370 | // issue a connection request 371 | go doConnectionRequest(cpe) 372 | } 373 | } else { 374 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", cpe)) 375 | } 376 | 377 | fmt.Println("i'm waiting for the message to be back") 378 | m = <-ch 379 | fmt.Println("it's back") 380 | 381 | // qui devo parsare la response e creare il summary "semplice" da visualizzare 382 | getParameterValues := new(cwmp.GetParameterValuesResponse) 383 | err = json.Unmarshal(m.Data, &getParameterValues) 384 | if err != nil { 385 | fmt.Println("error:", err) 386 | } 387 | 388 | summaryObject := map[string]map[string]string{} 389 | for area := range objectsToCheck { 390 | summaryObject[area] = make(map[string]string) 391 | } 392 | 393 | for idx := range getParameterValues.ParameterList { 394 | objectName := getParameterValues.ParameterList[idx].Name 395 | 396 | for area := range objectsToCheck { 397 | for leafIndex := range objectsToCheck[area] { 398 | leaf := objectsToCheck[area][leafIndex] 399 | if objectName == leaf { 400 | // leafName := strings.Split(leaf, ".") 401 | // summaryObject[area][leafName[len(leafName)-1]] = getParameterValues.ParameterList[idx].Value 402 | summaryObject[area][leaf] = getParameterValues.ParameterList[idx].Value 403 | } 404 | } 405 | } 406 | } 407 | 408 | m.MsgType = "SummaryResponse" 409 | dataSummary := map[string]map[string]string{} 410 | for area := range objectsToCheck { 411 | dataSummary[area] = summaryObject[area] 412 | } 413 | 414 | m.Data, _ = json.Marshal(dataSummary) 415 | 416 | if err := websocket.JSON.Send(ws, m); err != nil { 417 | fmt.Println("error while sending back answer:", err) 418 | } 419 | 420 | } else if m == "getMib" { 421 | cpe := data["cpe"].(string) 422 | req := Request{cpe, ws, cwmp.GetParameterNames(data["object"].(string), 1), func(msg *WsSendMessage) error { 423 | fmt.Println("sono nella callback") 424 | if err := websocket.JSON.Send(ws, msg); err != nil { 425 | fmt.Println("error while sending back answer:", err) 426 | } 427 | 428 | return err 429 | }} 430 | if _, exists := cpes[cpe]; exists { 431 | cpes[cpe].Queue.Enqueue(req) 432 | if cpes[cpe].State != "Connected" { 433 | // issue a connection request 434 | go doConnectionRequest(cpe) 435 | } 436 | } else { 437 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", cpe)) 438 | } 439 | } else if m == "setMib" { 440 | cpe := data["cpe"].(string) 441 | req := Request{cpe, ws, cwmp.SetParameterValues(data["object"].(string), data["value"].(string)), func(msg *WsSendMessage) error { 442 | fmt.Println("sono nella callback") 443 | if err := websocket.JSON.Send(ws, msg); err != nil { 444 | fmt.Println("error while sending back answer:", err) 445 | } 446 | 447 | return err 448 | }} 449 | if _, exists := cpes[cpe]; exists { 450 | cpes[cpe].Queue.Enqueue(req) 451 | if cpes[cpe].State != "Connected" { 452 | // issue a connection request 453 | go doConnectionRequest(cpe) 454 | } 455 | } else { 456 | fmt.Println(fmt.Sprintf("CPE with serial %s not found", cpe)) 457 | } 458 | } 459 | } 460 | fmt.Println("ws closed, leaving read routine") 461 | 462 | for i := range clients { 463 | if clients[i].ws == ws { 464 | clients = append(clients[:i], clients[i+1:]...) 465 | break 466 | } 467 | } 468 | } 469 | 470 | func sendAll(msg string) { 471 | for i := range clients { 472 | clients[i].Send(msg) 473 | } 474 | } 475 | 476 | func periodicWsChecker(c *Client, quit chan bool) { 477 | ticker := time.NewTicker(30 * time.Second) 478 | for { 479 | select { 480 | case <-ticker.C: 481 | // fmt.Println("new tick on client:", c) 482 | c.Send("ping") 483 | case <-quit: 484 | fmt.Println("received quit command for periodicWsChecker") 485 | ticker.Stop() 486 | return 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /cwmp/cwmp.go: -------------------------------------------------------------------------------- 1 | package cwmp 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/xml" 6 | "fmt" 7 | // "github.com/mxk/go-sqlite/sqlite3" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var soapEnv = "SOAP-ENV" 14 | 15 | type SoapEnvelope struct { 16 | XMLName xml.Name 17 | Header SoapHeader 18 | Body SoapBody 19 | } 20 | 21 | type SoapHeader struct { 22 | Id string `xml:"ID"` 23 | } 24 | type SoapBody struct { 25 | CWMPMessage CWMPMessage `xml:",any"` 26 | } 27 | 28 | type CWMPMessage struct { 29 | XMLName xml.Name 30 | } 31 | 32 | type EventStruct struct { 33 | EventCode string 34 | CommandKey string 35 | } 36 | 37 | type ParameterValueStruct struct { 38 | Name string 39 | Value string 40 | } 41 | 42 | type ParameterInfoStruct struct { 43 | Name string 44 | Writable string 45 | } 46 | 47 | type SetParameterValues_ struct { 48 | ParameterList []ParameterValueStruct `xml:"Body>SetParameterValues>ParameterList>ParameterValueStruct"` 49 | ParameterKey string `xml:"Body>SetParameterValues>ParameterKey>string"` 50 | } 51 | 52 | type GetParameterValues_ struct { 53 | ParameterNames []string `xml:"Body>GetParameterValues>ParameterNames>string"` 54 | } 55 | 56 | type GetParameterNames_ struct { 57 | ParameterPath []string `xml:"Body>GetParameterNames>ParameterPath"` 58 | NextLevel string `xml:"Body>GetParameterNames>NextLevel"` 59 | } 60 | 61 | type GetParameterValuesResponse struct { 62 | ParameterList []ParameterValueStruct `xml:"Body>GetParameterValuesResponse>ParameterList>ParameterValueStruct"` 63 | } 64 | 65 | type GetParameterNamesResponse struct { 66 | ParameterList []ParameterInfoStruct `xml:"Body>GetParameterNamesResponse>ParameterList>ParameterInfoStruct"` 67 | } 68 | 69 | type SetParameterValuesResponse struct { 70 | Status string `xml:"Body>SetParameterValuesResponse>Status"` 71 | } 72 | 73 | type CWMPInform struct { 74 | DeviceId DeviceID `xml:"Body>Inform>DeviceId"` 75 | Events []EventStruct `xml:"Body>Inform>Event>EventStruct"` 76 | ParameterList []ParameterValueStruct `xml:"Body>Inform>ParameterList>ParameterValueStruct"` 77 | } 78 | 79 | func (s *SoapEnvelope) KindOf() string { 80 | return s.Body.CWMPMessage.XMLName.Local 81 | } 82 | 83 | func (i *CWMPInform) GetEvents() string { 84 | res := "" 85 | for idx := range i.Events { 86 | res += i.Events[idx].EventCode 87 | } 88 | 89 | return res 90 | } 91 | 92 | func (i *CWMPInform) GetConnectionRequest() string { 93 | for idx := range i.ParameterList { 94 | // valid condition for both tr98 and tr181 95 | if strings.HasSuffix(i.ParameterList[idx].Name, "Device.ManagementServer.ConnectionRequestURL") { 96 | return i.ParameterList[idx].Value 97 | } 98 | } 99 | 100 | return "" 101 | } 102 | 103 | func (i *CWMPInform) GetSoftwareVersion() string { 104 | for idx := range i.ParameterList { 105 | if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.SoftwareVersion") { 106 | return i.ParameterList[idx].Value 107 | } 108 | } 109 | 110 | return "" 111 | } 112 | 113 | func (i *CWMPInform) GetHardwareVersion() string { 114 | for idx := range i.ParameterList { 115 | if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.HardwareVersion") { 116 | return i.ParameterList[idx].Value 117 | } 118 | } 119 | 120 | return "" 121 | } 122 | 123 | func (i *CWMPInform) GetDataModelType() string { 124 | if strings.HasPrefix(i.ParameterList[0].Name, "InternetGatewayDevice") { 125 | return "TR098" 126 | } else if strings.HasPrefix(i.ParameterList[0].Name, "Device") { 127 | return "TR181" 128 | } 129 | 130 | return "" 131 | } 132 | 133 | type DeviceID struct { 134 | Manufacturer string 135 | OUI string 136 | SerialNumber string 137 | } 138 | 139 | func InformResponse(mustUnderstand string) string { 140 | mustUnderstandHeader := "" 141 | if mustUnderstand != "" { 142 | mustUnderstandHeader = `` + mustUnderstand + `` 143 | } 144 | 145 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 146 | <` + soapEnv + `:Header>` + mustUnderstandHeader + ` 147 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 148 | 149 | 1 150 | 151 | 152 | ` 153 | } 154 | 155 | func GetParameterValues(leaf string) string { 156 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 157 | <` + soapEnv + `:Header/> 158 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 159 | 160 | 161 | ` + leaf + ` 162 | 163 | 164 | 165 | ` 166 | } 167 | 168 | func GetParameterMultiValues(leaves []string) string { 169 | msg := `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 170 | <` + soapEnv + `:Header/> 171 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 172 | 173 | ` 174 | 175 | for idx := range leaves { 176 | msg += `` + leaves[idx] + `` 177 | 178 | } 179 | msg += ` 180 | 181 | 182 | ` 183 | return msg 184 | } 185 | 186 | func SetParameterValues(leaf string, value string) string { 187 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 188 | <` + soapEnv + `:Header/> 189 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 190 | 191 | 192 | 193 | ` + leaf + ` 194 | ` + value + ` 195 | 196 | 197 | LC1309` + randToken() + ` 198 | 199 | 200 | ` 201 | } 202 | 203 | func randToken() string { 204 | b := make([]byte, 8) 205 | rand.Read(b) 206 | return fmt.Sprintf("%x", b) 207 | } 208 | 209 | func SetParameterMultiValues(data map[string]string) string { 210 | msg := `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 211 | <` + soapEnv + `:Header/> 212 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 213 | 214 | ` 215 | 216 | for key, value := range data { 217 | msg += ` 218 | ` + key + ` 219 | ` + value + ` 220 | ` 221 | } 222 | 223 | msg += ` 224 | LC1309` + randToken() + ` 225 | 226 | 227 | ` 228 | 229 | return msg 230 | } 231 | 232 | func GetParameterNames(leaf string, nextlevel int) string { 233 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 234 | <` + soapEnv + `:Header/> 235 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 236 | 237 | ` + leaf + ` 238 | ` + strconv.Itoa(nextlevel) + ` 239 | 240 | 241 | ` 242 | } 243 | 244 | func FactoryReset() string { 245 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 246 | <` + soapEnv + `:Header/> 247 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 248 | 249 | 250 | ` 251 | } 252 | 253 | func Download(filetype, url, username, password, filesize string) string { 254 | // 3 Vendor Configuration File 255 | // 1 Firmware Upgrade Image 256 | 257 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 258 | <` + soapEnv + `:Header/> 259 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 260 | 261 | MSDWK 262 | ` + filetype + ` 263 | ` + url + ` 264 | ` + username + ` 265 | ` + password + ` 266 | ` + filesize + ` 267 | 268 | 0 269 | 270 | 271 | 272 | 273 | ` 274 | } 275 | 276 | func CancelTransfer() string { 277 | return `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 278 | <` + soapEnv + `:Header/> 279 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 280 | 281 | 282 | 283 | 284 | ` 285 | } 286 | 287 | type TimeWindowStruct struct { 288 | WindowStart string 289 | WindowEnd string 290 | WindowMode string 291 | UserMessage string 292 | MaxRetries string 293 | } 294 | 295 | func (window *TimeWindowStruct) String() string { 296 | return ` 297 | ` + window.WindowStart + ` 298 | ` + window.WindowEnd + ` 299 | ` + window.WindowMode + ` 300 | ` + window.UserMessage + ` 301 | ` + window.MaxRetries + ` 302 | ` 303 | } 304 | 305 | func ScheduleDownload(filetype, url, username, password, filesize string, windowslist []fmt.Stringer) string { 306 | ret := `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 307 | <` + soapEnv + `:Header/> 308 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 309 | 310 | MSDWK 311 | ` + filetype + ` 312 | ` + url + ` 313 | ` + username + ` 314 | ` + password + ` 315 | ` + filesize + ` 316 | 317 | ` 318 | 319 | for _, op := range windowslist { 320 | ret += op.String() 321 | } 322 | 323 | ret += ` 324 | 325 | 326 | ` 327 | 328 | return ret 329 | } 330 | 331 | type InstallOpStruct struct { 332 | Url string 333 | Uuid string 334 | Username string 335 | Password string 336 | ExecutionEnvironment string 337 | } 338 | 339 | func (op *InstallOpStruct) String() string { 340 | return ` 341 | ` + op.Url + ` 342 | ` + op.Uuid + ` 343 | ` + op.Username + ` 344 | ` + op.Password + ` 345 | ` + op.ExecutionEnvironment + ` 346 | ` 347 | } 348 | 349 | type UpdateOpStruct struct { 350 | Uuid string 351 | Version string 352 | Url string 353 | Username string 354 | Password string 355 | } 356 | 357 | func (op *UpdateOpStruct) String() string { 358 | return ` 359 | ` + op.Uuid + ` 360 | ` + op.Version + ` 361 | ` + op.Url + ` 362 | ` + op.Username + ` 363 | ` + op.Password + ` 364 | ` 365 | } 366 | 367 | type UninstallOpStruct struct { 368 | Uuid string 369 | Version string 370 | ExecutionEnvironment string 371 | } 372 | 373 | func (op *UninstallOpStruct) String() string { 374 | return ` 375 | ` + op.Uuid + ` 376 | ` + op.Version + ` 377 | ` + op.ExecutionEnvironment + ` 378 | ` 379 | } 380 | 381 | func ChangeDuState(ops []fmt.Stringer) string { 382 | ret := `<` + soapEnv + `:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 383 | <` + soapEnv + `:Header/> 384 | <` + soapEnv + `:Body ` + soapEnv + `:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 385 | 386 | ` 387 | 388 | for _, op := range ops { 389 | ret += op.String() 390 | } 391 | 392 | ret += ` 393 | 394 | 395 | 396 | ` 397 | 398 | return ret 399 | } 400 | 401 | // CPE side 402 | 403 | func Inform(serial string) string { 404 | return `<` + soapEnv + `:Envelope xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0"><` + soapEnv + `:Header>5058 405 | <` + soapEnv + `:Body>ADB Broadband 406 | 0013C8 407 | VV5522 408 | PI234550701S199991-` + serial + ` 409 | 410 | 411 | 6 CONNECTION REQUEST 412 | 413 | 414 | 415 | 1 416 | ` + time.Now().Format(time.RFC3339) + ` 417 | 0 418 | 419 | InternetGatewayDevice.ManagementServer.ConnectionRequestURL 420 | http://localhost:7547/ConnectionRequest-` + serial + ` 421 | 422 | InternetGatewayDevice.ManagementServer.ParameterKey 423 | 424 | 425 | InternetGatewayDevice.DeviceSummary 426 | InternetGatewayDevice:1.2[](Baseline:1,EthernetLAN:1,WiFiLAN:1,ADSLWAN:1,EthernetWAN:1,QoS:1,QoSDynamicFlow:1,Bridging:1,Time:1,IPPing:1,TraceRoute:1,DeviceAssociation:1,UDPConnReq:1),VoiceService:1.0[1](TAEndpoint:1,SIPEndpoint:1) 427 | 428 | InternetGatewayDevice.DeviceInfo.HardwareVersion 429 | ` + serial + ` 430 | 431 | InternetGatewayDevice.DeviceInfo.ProvisioningCode 432 | ABCD 433 | 434 | InternetGatewayDevice.DeviceInfo.SoftwareVersion 435 | E_8.0.0.0002 436 | 437 | InternetGatewayDevice.DeviceInfo.SpecVersion 438 | 1.0 439 | 440 | InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress 441 | 12.0.0.10 442 | 443 | 444 | 445 | ` 446 | } 447 | 448 | /* 449 | func BuildGetParameterValuesResponse(serial string, leaves GetParameterValues_) string { 450 | ret := `<` + soapEnv + `:Envelope xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0"> 451 | <` + soapEnv + `:Header>3 452 | <` + soapEnv + `:Body>` 453 | 454 | db, _ := sqlite3.Open("/tmp/cpe.db") 455 | 456 | n_leaves := 0 457 | var temp string 458 | for _, leaf := range leaves.ParameterNames { 459 | sql := "select key, value, tipo from params where key like '" + leaf + "%'" 460 | for s, err := db.Query(sql); err == nil; err = s.Next() { 461 | n_leaves++ 462 | var key string 463 | var value string 464 | var tipo string 465 | s.Scan(&key, &value, &tipo) 466 | temp += ` 467 | ` + key + ` 468 | ` + value + ` 469 | ` 470 | } 471 | } 472 | 473 | ret += `` 474 | ret += temp 475 | ret += `` 476 | 477 | return ret 478 | } 479 | 480 | func BuildGetParameterNamesResponse(serial string, leaves GetParameterNames_) string { 481 | ret := `<` + soapEnv + `:Envelope xmlns:` + soapEnv + `="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0"> 482 | <` + soapEnv + `:Header>69 483 | <` + soapEnv + `:Body>` 484 | db, _ := sqlite3.Open("/tmp/cpe.db") 485 | 486 | obj := make(map[string]bool) 487 | var temp string 488 | for _, leaf := range leaves.ParameterPath { 489 | fmt.Println(leaf) 490 | sql := "select key, value, tipo from params where key like '" + leaf + "%'" 491 | for s, err := db.Query(sql); err == nil; err = s.Next() { 492 | var key string 493 | var value string 494 | var tipo string 495 | s.Scan(&key, &value, &tipo) 496 | var sp = strings.Split(strings.Split(key, leaf)[1], ".") 497 | nextlevel, _ := strconv.Atoi(leaves.NextLevel) 498 | if nextlevel == 0 { 499 | root := leaf 500 | obj[root] = true 501 | for idx := range sp { 502 | if idx == len(sp)-1 { 503 | root = root + sp[idx] 504 | } else { 505 | root = root + sp[idx] + "." 506 | } 507 | obj[root] = true 508 | } 509 | } else { 510 | if !obj[sp[0]] { 511 | if len(sp) > 1 { 512 | obj[leaf+sp[0]+"."] = true 513 | } else { 514 | obj[leaf+sp[0]] = true 515 | } 516 | 517 | } 518 | } 519 | 520 | } 521 | } 522 | 523 | for o := range obj { 524 | temp += ` 525 | ` + o + ` 526 | true 527 | ` 528 | } 529 | 530 | fmt.Println(len(obj)) 531 | ret += `` 532 | ret += temp 533 | ret += `` 534 | 535 | return ret 536 | } 537 | */ 538 | -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | --------------------------------------------------------------------------------