func NewHandler(config util.JsMap, logger *util.HekaLogger, store *storage.Storage) *Handler { return &Handler{config: config, logger: logger, logCat: "handler", hawk: &Hawk{logger: logger, config: config}, metrics: util.NewMetrics(util.MzGet(config, "metrics.prefix", "WMF")), store: store} }
// verify a Persona assertion using the config values // part of Handler for config & logging reasons func (self *Handler) verifyAssertion(assertion string) (userid, email string, err error) { var ok bool if util.MzGetFlag(self.config, "auth.disabled") { self.logger.Info(self.logCat, "### Skipping validation...", nil) return "user1", "*****@*****.**", nil } ver_url := util.MzGet(self.config, "persona.validater_url", "https://verifier.login.persona.org/verify") audience := util.MzGet(self.config, "persona.audience", "http://localhost:8080") res, err := http.PostForm(ver_url, url.Values{ "assertion": {assertion}, "audience": {audience}}) if err != nil { self.logger.Error(self.logCat, "Persona verification failed", util.Fields{"error": err.Error()}) return "", "", AuthorizationErr } buffer, err := parseBody(res.Body) if isOk, ok := buffer["status"]; !ok || isOk != "okay" { var errStr string if err != nil { errStr = err.Error() } else if _, ok = buffer["reason"]; ok { errStr = buffer["reason"].(string) } self.logger.Error(self.logCat, "Persona Auth Failed", util.Fields{"error": errStr, "buffer": fmt.Sprintf("%+v", buffer)}) return "", "", AuthorizationErr } if email, ok = buffer["email"].(string); !ok { self.logger.Error(self.logCat, "No email found in assertion", util.Fields{"assertion": fmt.Sprintf("%+v", buffer)}) return "", "", AuthorizationErr } if userid, ok = buffer["userid"].(string); !ok { hasher := sha256.New() hasher.Write([]byte(email)) userid = hex.EncodeToString(hasher.Sum(nil)) } return userid, email, nil }
func main() { flag.Parse() // Configuration config := util.MzGetConfig(*configFile) config["VERSION"] = VERSION runtime.GOMAXPROCS(runtime.NumCPU()) logger := util.NewHekaLogger(config) store := storage.New(config, logger) handlers := moztradamus.NewHandler(config, store, logger) // Signal handler sigChan := make(chan os.Signal) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGHUP, SIGUSR1) // Rest Config errChan := make(chan error) host := util.MzGet(config, "host", "localhost") port := util.MzGet(config, "port", "8080") var RESTMux *http.ServeMux = http.DefaultServeMux var verRoot = strings.SplitN(VERSION, ".", 2)[0] RESTMux.HandleFunc(fmt.Sprintf("/%s/ping/", verRoot), handlers.PingHandler) RESTMux.HandleFunc(fmt.Sprintf("/%s/poll/", verRoot), handlers.PollHandler) RESTMux.HandleFunc("/status/", handlers.StatusHandler) logger.Info("main", "startup...", nil) go func() { errChan <- http.ListenAndServe(host+":"+port, nil) }() select { case err := <-errChan: if err != nil { panic("ListenAndServe: " + err.Error()) } case <-sigChan: logger.Info("main", "Shutting down...", nil) } }
// -- main func main() { var configFile string flag.StringVar(&configFile, "config", "config.ini", "Configuration File") flag.Parse() log.Printf("Using config %s", configFile) config := util.MzGetConfig(configFile) // Convert the token_key from base64 (if present) if k, ok := config["token_key"]; ok { key, _ := base64.URLEncoding.DecodeString(k.(string)) config["token_key"] = key } logger = util.NewHekaLogger(config) simplepush.Clients = make(map[string]*simplepush.Client) // Initialize the common server. simplepush.InitServer(config, logger) // Register the handlers // each websocket gets it's own handler. http.HandleFunc("/update/", makeHandler(simplepush.UpdateHandler)) http.HandleFunc("/status/", makeHandler(simplepush.StatusHandler)) http.Handle("/", websocket.Handler(simplepush.PushSocketHandler)) // Config the server host := util.MzGet(config, "host", "localhost") port := util.MzGet(config, "port", "8080") // Hoist the main sail logger.Info("main", fmt.Sprintf("listening on %s:%s", host, port), nil) err := http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), nil) if err != nil { panic("ListenAndServe: " + err.Error()) } }
// Open the database. func Open(config util.JsMap, logger *util.HekaLogger) (store *Storage, err error) { dsn := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=%s", util.MzGet(config, "db.user", "user"), util.MzGet(config, "db.password", "password"), util.MzGet(config, "db.host", "localhost"), util.MzGet(config, "db.db", "wmf"), util.MzGet(config, "db.sslmode", "disable")) logCat := "storage" defExpry, err := strconv.ParseInt(util.MzGet(config, "db.default_expry", "1500"), 0, 64) if err != nil { defExpry = 1500 } db, err := sql.Open("postgres", dsn) if err != nil { return nil, err } db.SetMaxIdleConns(100) if err = db.Ping(); err != nil { return nil, err } store = &Storage{ config: config, logger: logger, logCat: logCat, defExpry: defExpry, dsn: dsn, db: db} if err = store.Init(); err != nil { return nil, err } return store, nil }
func main() { flags.ParseArgs(&opts, os.Args) // Configuration // defaults don't appear to work. if opts.ConfigFile == "" { opts.ConfigFile = "config.ini" } config := util.MzGetConfig(opts.ConfigFile) config["VERSION"] = VERSION if util.MzGetFlag(config, "aws.get_hostname") { if hostname, err := util.GetAWSPublicHostname(); err == nil { config["ws_hostname"] = hostname } } //TODO: Build out the partner cert pool if need be. // certpoo if opts.Profile != "" { log.Printf("Creating profile %s...\n", opts.Profile) f, err := os.Create(opts.Profile) if err != nil { log.Fatal("Profile creation failed:\n%s\n", err.Error()) return } defer func() { log.Printf("Closing profile...\n") pprof.StopCPUProfile() }() pprof.StartCPUProfile(f) } if opts.MemProfile != "" { defer func() { profFile, err := os.Create(opts.MemProfile) if err != nil { log.Fatal("Memory Profile creation failed:\n%s\n", err.Error()) return } pprof.WriteHeapProfile(profFile) profFile.Close() }() } runtime.GOMAXPROCS(runtime.NumCPU()) logger := util.NewHekaLogger(config) store, err := storage.Open(config, logger) if err != nil { logger.Error("main", "FAIL", nil) return } handlers := wmf.NewHandler(config, logger, store) // Signal handler sigChan := make(chan os.Signal) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGHUP, SIGUSR1) // Rest Config errChan := make(chan error) host := util.MzGet(config, "host", "localhost") port := util.MzGet(config, "port", "8080") var RESTMux *http.ServeMux = http.DefaultServeMux var WSMux *http.ServeMux = http.DefaultServeMux var verRoot = strings.SplitN(VERSION, ".", 2)[0] // REST calls // Device calls. RESTMux.HandleFunc(fmt.Sprintf("/%s/register/", verRoot), handlers.Register) RESTMux.HandleFunc(fmt.Sprintf("/%s/cmd/", verRoot), handlers.Cmd) // Web UI calls RESTMux.HandleFunc(fmt.Sprintf("/%s/queue/", verRoot), handlers.Queue) RESTMux.HandleFunc(fmt.Sprintf("/%s/state/", verRoot), handlers.State) RESTMux.HandleFunc("/static/", handlers.Static) RESTMux.HandleFunc("/metrics/", handlers.Metrics) // Operations call RESTMux.HandleFunc("/status/", handlers.Status) WSMux.Handle(fmt.Sprintf("/%s/ws/", verRoot), websocket.Handler(handlers.WSSocketHandler)) // Handle root calls as webUI RESTMux.HandleFunc("/", handlers.Index) logger.Info("main", "startup...", util.Fields{"host": host, "port": port}) go func() { errChan <- http.ListenAndServe(host+":"+port, nil) }() select { case err := <-errChan: if err != nil { panic("ListenAndServe: " + err.Error()) } case <-sigChan: logger.Info("main", "Shutting down...", nil) } }
// -- main func main() { var certFile string var keyFile string flag.Parse() config := util.MzGetConfig(*configFile) // The config file requires some customization and normalization config = simplepush.FixConfig(config) config["VERSION"] = VERSION runtime.GOMAXPROCS(runtime.NumCPU()) // Report what the app believes the current host to be, and what version. log.Printf("CurrentHost: %s, Version: %s", config["shard.current_host"], VERSION) // Metrics reporting if mprefix, ok := config["metrics.prefix"]; ok { simplepush.MetricsPrefix(mprefix.(string)) } if mstatsd, ok := config["metrics.statsd_target"]; ok { err := simplepush.MetricsStatsdTarget(mstatsd.(string)) if err != nil { log.Fatal("Couldn't create statsd client: ", err) } } // Only create profiles if requested. To view the application profiles, // see http://blog.golang.org/profiling-go-programs if *profile != "" { log.Printf("Creating profile...") f, err := os.Create(*profile) if err != nil { log.Fatal(err) } defer func() { log.Printf("Closing profile...") pprof.StopCPUProfile() }() pprof.StartCPUProfile(f) } if *memProfile != "" { defer func() { profFile, err := os.Create(*memProfile) if err != nil { log.Fatalln(err) } pprof.WriteHeapProfile(profFile) profFile.Close() }() } // Logging can be CPU intensive (note: variable reflection is VERY // CPU intensive. Avoid things like log.Printf("%v", someStruct) ) // If logging is specified as a command line flag, it overrides the // value specified in the config file. This allows short term logging // for operations. if *logging > 0 { config["logger.enable"] = "1" config["logger.filter"] = strconv.FormatInt(int64(*logging), 10) } if v, ok := config["logger.enable"]; ok { if v, _ := strconv.ParseBool(v.(string)); v { logger = util.NewHekaLogger(config) logger.Info("main", "Enabling full logger", nil) } } // Routing allows stand-alone instances to send updates between themselves. // Websock does not allow for native redirects in some browsers. Routing // allows websocket connections and updates to be handled by any server, // with the approriate notification sent. // // Note: While this is fairly primative, it works. There are more efficient // models and systems that could be used for this (e.g. 0mq, rabbit, etc.) // however those also add additional complexity to the server system. // Since this is mostly point-to-point (we know the host location to send // to), there wasn't much justification to add that complexity. // Obviously, this can and will change over time. route = &router.Router{ Port: util.MzGet(config, "shard.port", "3000"), Logger: logger, } defer func() { if route != nil { route.CloseAll() } }() // Currently, we're opting for a memcache "storage" mechanism, however // and key/value store would suffice. (bonus points if the records are // self expiring.) store = storage.New(config, logger) // Initialize the common server. simplepush.InitServer(config, logger) handlers := simplepush.NewHandler(config, logger, store, route) // Config the server var wsport string var wshost string var WSMux *http.ServeMux = http.DefaultServeMux var RESTMux *http.ServeMux = http.DefaultServeMux host := util.MzGet(config, "host", "localhost") port := util.MzGet(config, "port", "8080") // Register the handlers // each websocket gets it's own handler. if util.MzGet(config, "wsport", port) != port { wsport = util.MzGet(config, "wsport", port) wshost = util.MzGet(config, "wshost", host) WSMux = http.NewServeMux() } RESTMux.HandleFunc("/update/", handlers.UpdateHandler) RESTMux.HandleFunc("/status/", handlers.StatusHandler) RESTMux.HandleFunc("/realstatus/", handlers.RealStatusHandler) RESTMux.HandleFunc("/metrics/", handlers.MetricsHandler) WSMux.Handle("/", websocket.Handler(handlers.PushSocketHandler)) // Hoist the main sail. if logger != nil { logger.Info("main", fmt.Sprintf("listening on %s:%s", host, port), nil) } // Get the (optional) SSL certs if name, ok := config["ssl.certfile"]; ok { certFile = name.(string) } if name, ok := config["ssl.keyfile"]; ok { keyFile = name.(string) } wscertFile := util.MzGet(config, "ssl.ws.certfile", certFile) wskeyFile := util.MzGet(config, "ssl.ws.keyfile", keyFile) // wait for sigint sigChan := make(chan os.Signal) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGHUP, SIGUSR1) errChan := make(chan error) // Weigh the anchor! go func() { addr := host + ":" + port if len(certFile) > 0 && len(keyFile) > 0 { if logger != nil { logger.Info("main", "Using TLS", nil) } errChan <- http.ListenAndServeTLS(addr, certFile, keyFile, nil) } else { errChan <- http.ListenAndServe(addr, nil) } }() // Oh, we have a different context for WebSockets. Weigh that anchor too! if WSMux != RESTMux { if logger != nil { logger.Info("main", "Starting separate context for WS", nil) logger.Info("main", fmt.Sprintf("ws listen on %s:%s", wshost, wsport), nil) } go func() { wsaddr := wshost + ":" + wsport if len(wscertFile) > 0 && len(wskeyFile) > 0 { errChan <- http.ListenAndServeTLS(wsaddr, wscertFile, wskeyFile, WSMux) } else { errChan <- http.ListenAndServe(wsaddr, WSMux) } }() } // And we're underway! go route.HandleUpdates(updater) select { case err := <-errChan: if err != nil { panic("ListenAndServe: " + err.Error()) } case <-sigChan: if logger != nil { logger.Info("main", "Recieved signal, shutting down.", nil) } route.CloseAll() route = nil } }
func New(opts util.JsMap, logger *util.HekaLogger) *Storage { config = opts var ok bool var err error if configEndpoint, ok := config["elasticache.config_endpoint"]; ok { memcacheEndpoint, err := getElastiCacheEndpointsTimeout(configEndpoint.(string), 2) if err == nil { config["memcache.server"] = memcacheEndpoint } else { fmt.Println(err) if logger != nil { logger.Error("storage", "Elastisearch error.", util.Fields{"error": err.Error()}) } } } if _, ok = config["memcache.server"]; !ok { config["memcache.server"] = "127.0.0.1:11211" } timeout, err := time.ParseDuration(util.MzGet(config, "db.handle_timeout", "5s")) if err != nil { if logger != nil { logger.Error("storage", "Could not parse db.handle_timeout", util.Fields{"err": err.Error()}) } timeout = 10 * time.Second } if _, ok = config["memcache.pool_size"]; !ok { config["memcache.pool_size"] = "100" } if config["memcache.pool_size"], err = strconv.ParseInt(config["memcache.pool_size"].(string), 0, 0); err != nil { config["memcache.pool_size"] = 100 } poolSize := int(config["memcache.pool_size"].(int64)) if _, ok = config["db.timeout_live"]; !ok { config["db.timeout_live"] = "259200" } if _, ok = config["db.timeout_reg"]; !ok { config["db.timeout_reg"] = "10800" } if _, ok = config["db.timeout_del"]; !ok { config["db.timeout_del"] = "86400" } if _, ok = config["shard.default_host"]; !ok { config["shard.default_host"] = "localhost" } if _, ok = config["shard.current_host"]; !ok { config["shard.current_host"] = config["shard.default_host"] } if _, ok = config["shard.prefix"]; !ok { config["shard.prefix"] = "_h-" } /* i, err := strconv.ParseInt(util.MzGet(config, "memcache.max_pool_size", "400"), 0, 10) if err != nil { if logger != nil { logger.Error("storage", "Could not parse memcache.max_pool_size", util.Fields{"error": err.Error()}) } mcsMaxPoolSize = 400 } mcsMaxPoolSize = int32(i) */ if logger != nil { logger.Info("storage", "Creating new gomc handler", nil) } // do NOT include any spaces servers := strings.Split( no_whitespace.Replace(config["memcache.server"].(string)), ",") mcs := make(chan gomc.Client, poolSize) for i := 0; i < poolSize; i++ { mcs <- newMC(servers, config, logger) } return &Storage{ mcs: mcs, config: config, logger: logger, mc_timeout: timeout, servers: servers, } }
// -- REST func (self *Handler) UpdateHandler(resp http.ResponseWriter, req *http.Request) { // Handle the version updates. var err error var port string var vers int64 if ClientCount() > self.config["max_connections"].(int) { if self.logger != nil { if toomany == 0 { atomic.StoreInt32(&toomany, 1) self.logger.Error("handler", "Socket Count Exceeded", nil) } } MetricIncrement("too many connections") http.Error(resp, "{\"error\": \"Server unavailable\"}", http.StatusServiceUnavailable) return } if toomany != 0 { atomic.StoreInt32(&toomany, 0) } timer := time.Now() filter := regexp.MustCompile("[^\\w-\\.\\=]") if self.logger != nil { self.logger.Debug("update", "Handling Update", util.Fields{"path": req.URL.Path}) } if self.logger != nil { self.logger.Debug("update", "=========== UPDATE ====", nil) } defer func() { if self.logger != nil { self.logger.Debug("update", "+++++++++++++ DONE +++", nil) } }() if req.Method != "PUT" { http.Error(resp, "", http.StatusMethodNotAllowed) return } svers := req.FormValue("version") if svers != "" { vers, err = strconv.ParseInt(svers, 10, 64) if err != nil || vers < 0 { http.Error(resp, "\"Invalid Version\"", http.StatusBadRequest) return } } else { vers = time.Now().UTC().Unix() } elements := strings.Split(req.URL.Path, "/") pk := elements[len(elements)-1] if len(pk) == 0 { if self.logger != nil { self.logger.Error("update", "No token, rejecting request", util.Fields{"remoteAddr": req.RemoteAddr, "path": req.URL.Path}) } http.Error(resp, "Token not found", http.StatusNotFound) return } if token, ok := self.config["token_key"]; ok && len(token.([]uint8)) > 0 { if self.logger != nil { // Note: dumping the []uint8 keys can produce terminal glitches self.logger.Debug("main", "Decoding...", nil) } var err error bpk, err := Decode(token.([]byte), pk) if err != nil { if self.logger != nil { self.logger.Error("update", "Could not decode token", util.Fields{"primarykey": pk, "remoteAddr": req.RemoteAddr, "path": req.URL.Path, "error": ErrStr(err)}) } http.Error(resp, "", http.StatusNotFound) return } pk = strings.TrimSpace(string(bpk)) } if filter.Find([]byte(pk)) != nil { if self.logger != nil { self.logger.Error("update", "Invalid token for update", util.Fields{"token": pk, "path": req.URL.Path}) } http.Error(resp, "Invalid Token", http.StatusNotFound) return } uaid, chid, err := storage.ResolvePK(pk) if err != nil { if self.logger != nil { self.logger.Error("update", "Could not resolve PK", util.Fields{"primaryKey": pk, "path": req.URL.Path, "error": ErrStr(err)}) } return } if chid == "" { if self.logger != nil { self.logger.Error("update", "Incomplete primary key", util.Fields{"uaid": uaid, "channelID": chid, "remoteAddr": req.RemoteAddr}) } return } //log.Printf("<< %s.%s = %d", uaid, chid, vers) if iport, ok := self.config["port"]; ok { port = iport.(string) } if port == "80" { port = "" } if port != "" { port = ":" + port } currentHost := util.MzGet(self.config, "shard.current_host", "localhost") host, err := self.store.GetUAIDHost(uaid) if err != nil { if self.logger != nil { self.logger.Error("update", "Could not discover host for UAID", util.Fields{"uaid": uaid, "error": err.Error()}) } host = util.MzGet(self.config, "shard.defaultHost", "localhost") } if util.MzGetFlag(self.config, "shard.do_proxy") { if host != currentHost && host != "localhost" { if self.logger != nil { self.logger.Info("update", "Proxying request for UAID", util.Fields{"uaid": uaid, "destination": host + port}) } // Use tcp routing. if util.MzGetFlag(self.config, "shard.router") { // If there was an error routing the update, don't // tell the AppServer. Chances are it's temporary, and // the client will get the update on next refresh/reconnect self.router.SendUpdate(host, uaid, chid, vers, timer) } else { proto := "http" if len(util.MzGet(self.config, "ssl.certfile", "")) > 0 { proto = "https" } err = proxyNotification(proto, host+port, req.URL.Path, vers) if err != nil && self.logger != nil { self.logger.Error("update", "Proxy failed", util.Fields{ "uaid": uaid, "destination": host + port, "error": err.Error()}) } } MetricIncrement("routing update: out") if err != nil { http.Error(resp, err.Error(), 500) } else { resp.Write([]byte("Ok")) } return } } if self.logger != nil { defer func(uaid, chid, path string, timer time.Time) { self.logger.Info("timer", "Client Update complete", util.Fields{ "uaid": uaid, "path": req.URL.Path, "channelID": chid, "duration": strconv.FormatInt(time.Now().Sub(timer).Nanoseconds(), 10)}) }(uaid, chid, req.URL.Path, timer) self.logger.Info("update", "setting version for ChannelID", util.Fields{"uaid": uaid, "channelID": chid, "version": strconv.FormatInt(vers, 10)}) } err = self.store.UpdateChannel(pk, vers) if err != nil { if self.logger != nil { self.logger.Error("update", "Cound not update channel", util.Fields{"UAID": uaid, "channelID": chid, "version": strconv.FormatInt(vers, 10), "error": err.Error()}) } status, _ := sperrors.ErrToStatus(err) http.Error(resp, "Could not update channel version", status) return } resp.Header().Set("Content-Type", "application/json") resp.Write([]byte("{}")) // Ping the appropriate server if client, ok := Clients[uaid]; ok { Flush(client, chid, int64(vers)) } MetricIncrement("update channel") return }
// user login functions func (self *Handler) Index(resp http.ResponseWriter, req *http.Request) { /* Handle a user login to the web UI */ // This should be handled by an nginx rule. if strings.Contains(req.URL.Path, "/static/") { if strings.Contains(req.URL.Path, "..") { return } body, err := ioutil.ReadFile("." + req.URL.Path) if err == nil { resp.Write(body) } return } self.logCat = "handler:Index" var data struct { ProductName string UserId string MapKey string DeviceList []storage.DeviceList Device *storage.Device Host map[string]string } var err error // Get this from the config file? data.ProductName = util.MzGet(self.config, "productname", "Where's My Fox") data.MapKey = util.MzGet(self.config, "mapbox.key", "") // host information (for websocket callback) data.Host = make(map[string]string) data.Host["Hostname"] = util.MzGet(self.config, "ws_hostname", "localhost") // get the cached session info (if present) // will also resolve assertions and other bits to get user and dev info. sessionInfo, err := self.getSessionInfo(req) if err == nil { // we have user info, use it. data.UserId = sessionInfo.UserId if sessionInfo.DeviceId == "" { data.DeviceList, err = self.store.GetDevicesForUser(data.UserId) if err != nil { self.logger.Error(self.logCat, "Could not get user devices", util.Fields{"error": err.Error(), "user": data.UserId}) } if len(data.DeviceList) == 1 { sessionInfo.DeviceId = (data.DeviceList[0]).ID data.DeviceList = nil } } if sessionInfo.DeviceId != "" { data.Device, err = self.store.GetDeviceInfo(sessionInfo.DeviceId) if err != nil { self.logger.Error(self.logCat, "Could not get device info", util.Fields{"error": err.Error(), "user": data.UserId}) if file, err := ioutil.ReadFile("static/error.html"); err == nil { resp.Write(file) } return } data.Device.PreviousPositions, err = self.store.GetPositions(sessionInfo.DeviceId) if err != nil { self.logger.Error(self.logCat, "Could not get device's position information", util.Fields{"error": err.Error(), "user": data.UserId, "device": sessionInfo.DeviceId}) return } } } // render the page from the template tmpl, err := template.New("index.html").ParseFiles("static/index.html") if err != nil { // TODO: display error self.logger.Error(self.logCat, "Could not display index page", util.Fields{"error": err.Error(), "user": data.UserId}) if file, err := ioutil.ReadFile("static/error.html"); err == nil { resp.Write(file) } return } setSessionInfo(resp, sessionInfo) err = tmpl.Execute(resp, data) if err != nil { self.logger.Error(self.logCat, "Could not execute template", util.Fields{"error": err.Error()}) } self.metrics.Increment("page.index") return }
func (self *Handler) Queue(resp http.ResponseWriter, req *http.Request) { /* Queue commands for the device. */ var err error var lbody int rep := make(reply_t) self.logCat = "handler:Queue" resp.Header().Set("Content-Type", "application/json") deviceId := getDevFromUrl(req) if deviceId == "" { self.logger.Error(self.logCat, "Invalid call (No device id)", nil) http.Error(resp, "Unauthorized", 401) return } userId, _, err := self.getUser(req) if err != nil { self.logger.Error(self.logCat, "No userid", nil) http.Error(resp, "Unauthorized", 401) return } if userId == "" { http.Error(resp, "Unauthorized", 401) return } devRec, err := self.store.GetDeviceInfo(deviceId) if devRec.User != userId { http.Error(resp, "Unauthorized", 401) return } if devRec == nil { self.logger.Error(self.logCat, "Queue:User requested unknown device", util.Fields{ "deviceId": deviceId, "userId": userId}) http.Error(resp, "Unauthorized", 401) return } if err != nil { switch err { default: self.logger.Error(self.logCat, "Cmd:Unhandled Error", util.Fields{ "error": err.Error(), "deviceId": deviceId}) http.Error(resp, "Unauthorized", 401) } return } //decode the body var body = make([]byte, req.ContentLength) lbody, err = req.Body.Read(body) if err != nil { self.logger.Error(self.logCat, "Could not read body", util.Fields{"error": err.Error()}) http.Error(resp, "Invalid", 400) return } self.logger.Info(self.logCat, "Handling cmd", util.Fields{ "cmd": string(body), "lbody": fmt.Sprintf("%d", lbody), }) if lbody > 0 { reply := make(reply_t) merr := json.Unmarshal(body, &reply) // merr := json.Unmarshal(body, &reply) if merr != nil { self.logger.Error(self.logCat, "Could not unmarshal data", util.Fields{ "error": merr.Error(), "body": string(body)}) http.Error(resp, "Server Error", 500) return } for cmd, args := range reply { // sanitize values. var v interface{} var ok bool c := strings.ToLower(string(cmd[0])) if !strings.Contains(devRec.Accepts, c) { // skip unacceptable command self.logger.Warn(self.logCat, "Agent does not accept command", util.Fields{"unacceptable": c, "acceptable": devRec.Accepts}) rep["error"] = 422 rep["cmd"] = cmd continue } rargs := args.(map[string]interface{}) switch c { case "l": if v, ok = rargs["c"]; ok { max, err := strconv.ParseInt(util.MzGet(self.config, "cmd.c.max", "9999"), 10, 64) if err != nil { max = 9999 } vs := v.(string) rargs["c"] = strings.Map(digitsOnly, vs[:minInt(4, len(vs))]) rargs["c"] = self.rangeCheck(rargs["c"].(string), 0, max) } if v, ok = rargs["m"]; ok { vs := v.(string) rargs["m"] = strings.Map(asciiOnly, vs[:minInt(100, len(vs))]) } case "r", "t": if v, ok = rargs["d"]; ok { max, err := strconv.ParseInt( util.MzGet(self.config, "cmd."+c+".max", "10500"), 10, 64) if err != nil { max = 10500 } vs := v.(string) rargs["d"] = strings.Map(digitsOnly, vs) rargs["d"] = self.rangeCheck(rargs["d"].(string), 0, max) } case "e": rargs = storage.Unstructured{} default: http.Error(resp, "Invalid Command", 400) return } fixed, err := json.Marshal(storage.Unstructured{c: rargs}) if err != nil { // Log the error self.logger.Error(self.logCat, "Error handling command", util.Fields{"error": err.Error(), "command": string(cmd), "device": deviceId, "args": fmt.Sprintf("%v", rargs)}) http.Error(resp, "Server Error", http.StatusServiceUnavailable) } err = self.store.StoreCommand(deviceId, string(fixed)) if err != nil { // Log the error self.logger.Error(self.logCat, "Error storing command", util.Fields{"error": err.Error(), "command": string(cmd), "device": deviceId, "args": fmt.Sprintf("%v", args)}) http.Error(resp, "Server Error", http.StatusServiceUnavailable) } // trigger the push self.metrics.Increment("cmd.store." + c) self.metrics.Increment("push.send") err = SendPush(devRec, &self.config) if err != nil { self.logger.Error(self.logCat, "Could not send Push", util.Fields{"error": err.Error(), "pushUrl": devRec.PushUrl}) http.Error(resp, "Server Error", http.StatusServiceUnavailable) } } } repl, _ := json.Marshal(rep) resp.Write(repl) }