func (self *Hawk) genHash(req *http.Request, body string) (hash string) { contentType := req.Header.Get("Content-Type") if contentType == "" { contentType = "text/plain" } // Something is appending the chartype to the content. this can throw off // the hash generator. // Client creates mac using "application/json", // we get "application/json;charset=UTF8" which brings much sadness. contentType = (strings.Split(contentType, ";"))[0] nbody := strings.Replace(body, "\\", "\\\\", -1) nbody = strings.Replace(nbody, "\n", "\\n", -1) marshalStr := fmt.Sprintf("%s\n%s\n%s\n", "hawk.1.payload", contentType, nbody) sha := sha256.Sum256([]byte(marshalStr)) hash = base64.StdEncoding.EncodeToString([]byte(sha[:32])) if util.MzGetFlag(self.config, "hawk.show_hash") { self.logger.Debug("hawk", "genHash", util.Fields{"marshalStr": marshalStr, "hash": hash}) } return hash }
func FixConfig(config util.JsMap) util.JsMap { if _, ok := config["shard.current_host"]; !ok { currentHost := "localhost" if val := os.Getenv("HOST"); len(val) > 0 { currentHost = val } else { if util.MzGetFlag(config, "shard.use_aws_host") { var awsHost string var err error awsHost, err = awsGetPublicHostname() if err == nil { currentHost = awsHost } } } config["shard.current_host"] = currentHost } // Convert the token_key from base64 (if present) if k, ok := config["token_key"]; ok { key, err := base64.URLEncoding.DecodeString(k.(string)) if err != nil { log.Fatal(err) } config["token_key"] = key } DEFAULT_MAX_CONNECTIONS := 1000 config["heka.current_host"] = config["shard.current_host"] if _, ok := config["max_connections"]; ok { var err error val := config["max_connections"].(string) ival, err := strconv.ParseInt(val, 10, 0) if err != nil { config["max_connections"] = DEFAULT_MAX_CONNECTIONS } else { config["max_connections"] = int(ival) } } else { config["max_connections"] = DEFAULT_MAX_CONNECTIONS } return config }
/* Things to check: * Are all values being sent? (e.g. extra, time, secret) * Do the secrets match? * is the other format string formatted correctly? (two \n before extra, 0 after) */ func (self *Hawk) GenerateSignature(req *http.Request, extra, body, secret string) (err error) { // create path if self.Path == "" { self.Path = getFullPath(req) } // figure out port if self.Host == "" { self.Host, self.Port = self.getHostPort(req) } if self.Nonce == "" { self.Nonce = GenNonce(6) } if self.Time == "" { self.Time = strconv.FormatInt(time.Now().UTC().Unix(), 10) } if self.Method == "" { self.Method = strings.ToUpper(req.Method) } if self.Hash == "" { self.Hash = self.genHash(req, body) } marshalStr := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", "hawk.1.header", self.Time, self.Nonce, strings.ToUpper(self.Method), self.Path, strings.ToLower(self.Host), self.Port, self.Hash, extra) if util.MzGetFlag(self.config, "hawk.show_hash") { self.logger.Debug("hawk", "Marshal", util.Fields{"marshalStr": marshalStr, "secret": secret}) } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(marshalStr)) self.Signature = base64.StdEncoding.EncodeToString(mac.Sum(nil)) return err }
// 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 }
// get the host and port from the request func (self *Hawk) getHostPort(req *http.Request) (host, port string) { elements := strings.Split(req.Host, ":") host = elements[0] if len(elements) == 2 { port = elements[1] } if port == "" || util.MzGetFlag(self.config, "override_port") { switch { // because nginx proxies, don't take the :port at face value //case len(elements) > 1: // port = elements[1] case req.URL.Scheme == "https": port = "443" default: port = "80" } } return host, port }
func (self *Worker) Ping(sock *PushWS, buffer interface{}) (err error) { if self.pingInt > 0 && int64(self.lastPing.Sub(time.Now()).Seconds()) < self.pingInt { source := sock.Socket.Config().Origin if self.logger != nil { self.logger.Error("worker", fmt.Sprintf("Client sending too many pings %s", source), nil) } else { log.Printf("Worker: Client sending too many pings. %s", source) } self.stopped = true return sperrors.TooManyPingsError } data := buffer.(util.JsMap) if util.MzGetFlag(self.config, "push.long_pongs") { websocket.JSON.Send(sock.Socket, util.JsMap{ "messageType": data["messageType"], "status": 200}) } else { websocket.Message.Send(sock.Socket, "{}") } return 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) } }
// -- 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 }
func (self *Handler) Cmd(resp http.ResponseWriter, req *http.Request) { /* Log response and Pass the latest command off to the device. */ var err error var l int self.logCat = "handler:Cmd" 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 } devRec, err := self.store.GetDeviceInfo(deviceId) if err != nil { switch err { case storage.ErrUnknownDevice: self.logger.Error(self.logCat, "Cmd:Unknown device requesting cmd", util.Fields{ "deviceId": deviceId}) http.Error(resp, "Unauthorized", 401) 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) l, 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 } //validate the Hawk header if util.MzGetFlag(self.config, "hawk.disabled") == false { // Remote Hawk rhawk := Hawk{logger: self.logger} // Local Hawk lhawk := Hawk{logger: self.logger} // Get the remote signature from the header err = rhawk.ParseAuthHeader(req, self.logger) if err != nil { self.logger.Error(self.logCat, "Could not parse Hawk header", util.Fields{"error": err.Error()}) http.Error(resp, "Unauthorized", 401) return } // Generate the comparator signature from what we know. lhawk.Nonce = rhawk.Nonce lhawk.Time = rhawk.Time //lhawk.Hash = rhawk.Hash err = lhawk.GenerateSignature(req, rhawk.Extra, string(body), devRec.Secret) if err != nil { self.logger.Error(self.logCat, "Could not verify sig", util.Fields{"error": err.Error()}) http.Error(resp, "Unauthorized", 401) return } // Do they match? if !lhawk.Compare(rhawk.Signature) { self.logger.Error(self.logCat, "Cmd:Invalid Hawk Signature", util.Fields{ "expecting": lhawk.Signature, "got": rhawk.Signature, }) http.Error(resp, "Unauthorized", 401) return } } // Do the command. self.logger.Info(self.logCat, "Handling cmd", util.Fields{ "cmd": string(body), "length": fmt.Sprintf("%d", l), }) if l > 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 { c := strings.ToLower(string(cmd[0])) if !strings.Contains(devRec.Accepts, c) { self.logger.Warn(self.logCat, "Unacceptable Command", util.Fields{"unacceptable": c, "acceptable": devRec.Accepts}) continue } self.metrics.Increment("cmd.received." + string(c)) switch c { case "l", "r", "m", "e": err = self.store.Touch(deviceId) self.updatePage(deviceId, args.(map[string]interface{}), false) case "h": argl := make(reply_t) argl[string(cmd)] = isTrue(args) self.updatePage(deviceId, argl, false) case "t": // track err = self.updatePage(deviceId, args.(map[string]interface{}), true) // store tracking info. case "q": // User has quit, nuke what we know. if util.MzGetFlag(self.config, "cmd.q.allow") { err = self.store.DeleteDevice(deviceId) } } 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", args)}) http.Error(resp, "Server Error", http.StatusServiceUnavailable) return } } } // reply with pending commands // cmd, err := self.store.GetPending(deviceId) var output []byte = []byte(cmd) if err != nil { self.logger.Error(self.logCat, "Could not send commands", util.Fields{"error": err.Error()}) http.Error(resp, "Server Error", http.StatusServiceUnavailable) } if output == nil || len(output) < 2 { output = []byte("{}") } authHeader := self.hawk.AsHeader(req, devRec.User, string(output), "", devRec.Secret) resp.Header().Add("Authorization", authHeader) // total cheat to get the command without parsing the cmd data. if len(cmd) > 2 { self.metrics.Increment("cmd.send." + string(cmd[2])) } resp.Write(output) }