package event import ( "bytes" "context" "encoding/json" "errors" "fmt" "log" "math/rand" "net/http" "strconv" "time" "git.nutfactory.org/hoernschen/Matrix/config" "git.nutfactory.org/hoernschen/Matrix/entities/device" "git.nutfactory.org/hoernschen/Matrix/entities/user" "git.nutfactory.org/hoernschen/Matrix/utils" //"github.com/cenkalti/backoff/v4" "github.com/gorilla/mux" "github.com/lestrrat-go/backoff" ) func New( roomId string, sender string, origin string, timestamp int64, eventType string, stateKey string, content string, txnId string, ) (err error, newEvent *Event) { err, eventId := utils.CreateUUID() if err != nil { return } id := generateEventId(eventId) newEvent = &Event{ Id: id, RoomId: roomId, Sender: sender, Origin: origin, Timestamp: timestamp, EventType: eventType, StateKey: stateKey, Content: content, Unsigned: UnsignedData{ TransactionId: txnId, }, } newEvent.AuthEventHashes, err = GetAuthEvents(newEvent) if err != nil { return } if eventType != "m.room.create" { var depth int newEvent.PrevEventHashes, depth, err = ReadEventsWithoutChild(roomId) if err != nil { return } newEvent.Depth = depth + 1 } newEvent.AuthEventHashes, err = GetAuthEvents(newEvent) if err != nil { return } newEventBytesForHash, err := json.Marshal(newEvent) if err != nil { return } err, newEvent.Hashes.SHA256 = utils.Hash(newEventBytesForHash) if err != nil { return } newEvent.Unsigned = UnsignedData{} newEventBytesForSign, err := json.Marshal(newEvent) if err != nil { return } newEvent.Signatures = utils.SignContent(newEventBytesForSign) newEvent.Unsigned = UnsignedData{ TransactionId: txnId, } return } func SendMessageHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") request := SendMessageRequest{} errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } decoder := json.NewDecoder(r.Body) err = decoder.Decode(&request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Could not parse JSON: %s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] eventType := vars["eventType"] txnId := vars["txnId"] if roomId == "" || eventType == "" || txnId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Parameter"}); err != nil { panic(err) } return } content, _ := json.Marshal(request) err, newEvent := New( roomId, foundUser.Id, config.Homeserver, time.Now().Unix(), eventType, foundUser.Id, string(content), txnId, ) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Creating Event: %s", err)}); err != nil { panic(err) } return } err = CreateEvent(newEvent, txnId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Database Error: %s", err)}); err != nil { panic(err) } return } transaction := &Transaction{ Id: txnId, Origin: config.Homeserver, Timestamp: time.Now().Unix(), PDUS: []*Event{newEvent}, } servers, err := ReadServers(roomId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Database Error: %s", err)}); err != nil { panic(err) } return } for _, server := range servers { if server != config.Homeserver { log.Printf("Send Transaction to %s", server) /* operation := func() error { return SendTransaction(transaction, server, config.HttpString, config.AuthentificationCheck) } notify := func(err error, duration time.Duration) { log.Printf("Error Sending Transaction, retrying in %ss: %s", duration/1000000000, err) } backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify) */ go retryTransaction(transaction, server, config.HttpString, config.AuthentificationCheck) } } response := createEventResponse{ EventId: newEvent.Id, } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func retryTransaction(transactionToSend *Transaction, server string, httpString string, authCheck bool) (err error) { b, cancel := config.BackoffPolicy.Start(context.Background()) defer cancel() for backoff.Continue(b) { err := SendTransaction(transactionToSend, server, httpString, authCheck) if err == nil { return nil } } err = errors.New("Not able to send transaction") return } func CreateStateEventHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] eventType := vars["eventType"] stateKey := vars["stateKey"] if roomId == "" || eventType == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Parameter"}); err != nil { panic(err) } return } buf := new(bytes.Buffer) buf.ReadFrom(r.Body) content := buf.String() err, newEvent := New( roomId, foundUser.Id, config.Homeserver, time.Now().Unix(), eventType, stateKey, content, "", ) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Creating Event: %s", err)}); err != nil { panic(err) } return } err = CreateEvent(newEvent, "") if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Database Error: %s", err)}); err != nil { panic(err) } return } err, txnId := utils.CreateUUID() transaction := &Transaction{ Id: txnId, Origin: config.Homeserver, Timestamp: time.Now().Unix(), PDUS: []*Event{newEvent}, } servers, err := ReadServers(roomId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Database Error: %s", err)}); err != nil { panic(err) } return } for _, server := range servers { /*operation := func() error { return SendTransaction(transaction, server, config.HttpString, config.AuthentificationCheck) } notify := func(err error, duration time.Duration) { log.Printf("Error Sending Transaction, retrying in %ss: %s", duration/1000000000, err) } go backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)*/ go retryTransaction(transaction, server, config.HttpString, config.AuthentificationCheck) } response := createEventResponse{ EventId: newEvent.Id, } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func GetEventUserHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") request := getEventRequest{} errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } decoder := json.NewDecoder(r.Body) err = decoder.Decode(&request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Could not parse JSON: %s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] eventId := vars["eventId"] if roomId == "" || eventId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Parameter"}); err != nil { panic(err) } return } foundEvent, err := ReadEvent(eventId) if err != nil || foundEvent == nil { w.WriteHeader(http.StatusNotFound) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_NOT_FOUND", ErrorMessage: fmt.Sprintf("Event not found. %s", err)}); err != nil { panic(err) } return } response := getEventResponse{ Content: foundEvent.Content, EventType: foundEvent.EventType, } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func GetStateEventHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] eventType := vars["eventType"] stateKey := vars["stateKey"] if roomId == "" || eventType == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Parameter"}); err != nil { panic(err) } return } foundEvent, err := ReadStateEvent(roomId, eventType, stateKey) if err != nil || foundEvent == nil { w.WriteHeader(http.StatusNotFound) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_NOT_FOUND", ErrorMessage: fmt.Sprintf("Event not found. %s", err)}); err != nil { panic(err) } return } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(foundEvent.Content); err != nil { panic(err) } } func SyncEventsServerHandler(w http.ResponseWriter, r *http.Request) { packetLossNumber := rand.Intn(100) if packetLossNumber > config.Packetloss { w.Header().Set("Content-Type", "application/json; charset=UTF-8") buf := new(bytes.Buffer) buf.ReadFrom(r.Body) request := syncEventsServerRequest{} errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } _ = utils.CheckAuthHeader(r, buf.String()) /*if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return }*/ err := json.Unmarshal(buf.Bytes(), &request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("SyncEventsServerHandler Could not parse JSON Request: %s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) txnId := vars["txnId"] if txnId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "SyncEventsServerHandler Missing Parameter"}); err != nil { panic(err) } return } response := syncEventsServerResponse{ PDUs: make(map[string]pduProcessingResult), } missingEventIds := make(map[string][]string) for _, pdu := range request.PDUs { signatureValid := CheckSignature(*pdu) if !signatureValid { log.Printf("Wrong Signature for Event %s", pdu.Id) response.PDUs[pdu.Id] = pduProcessingResult{ProcessingError: "Signature not valid"} //continue } authEventsValid, err := CheckAuthEvents(pdu) if !authEventsValid || err != nil { log.Printf("Wrong Auth Events for Event %s", pdu.Id) response.PDUs[pdu.Id] = pduProcessingResult{ProcessingError: fmt.Sprintf("Error in Auth Check: %s", err)} //continue } missingParentIds, err := CheckParents(pdu) if len(missingParentIds) > 0 || err != nil { response.PDUs[pdu.Id] = pduProcessingResult{ProcessingError: fmt.Sprintf("Error in Parents Check: %s", err)} for _, parentId := range missingParentIds { missingEventIds[pdu.RoomId] = append(missingEventIds[pdu.RoomId], parentId) } } err = HandleEvent(pdu, txnId) if err != nil { response.PDUs[pdu.Id] = pduProcessingResult{ProcessingError: fmt.Sprintf("Error in Event-Handling: %s", err)} continue } response.PDUs[pdu.Id] = pduProcessingResult{} } if len(missingEventIds) > 0 { for roomId, eventIds := range missingEventIds { Backfill(eventIds, roomId, request.Origin, config.HttpString, config.AuthentificationCheck) } } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } else { log.Println("Blocking Response") } } func BackfillHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") errResponse := utils.CheckAuthHeader(r, "") if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] if roomId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Parameter"}); err != nil { panic(err) } return } limit := 50 eventIds, ok := r.URL.Query()["v"] if !ok || eventIds[0] == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Parameter"}); err != nil { panic(err) } return } limitString := r.URL.Query().Get("limit") if limitString != "" { limit, _ = strconv.Atoi(limitString) } pdus := []*Event{} for len(pdus) < limit { newEventIds := []string{} for _, eventId := range eventIds { foundEvent, err := ReadEvent(eventId) if err != nil || foundEvent == nil { continue } for newEventId, _ := range foundEvent.PrevEventHashes { newEventIds = append(newEventIds, newEventId) } pdus = append(pdus, foundEvent) } eventIds = newEventIds } response := backfillResponse{ Origin: config.Homeserver, Timestamp: time.Now().Unix(), PDUs: pdus, } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func SendTransaction(transaction *Transaction, homeserver string, httpString string, authentification bool) (err error) { requestUrl := fmt.Sprintf("%s://%s/_matrix/federation/v1/send/%s?", httpString, homeserver, transaction.Id) request := syncEventsServerRequest{ Origin: transaction.Origin, Timestamp: transaction.Timestamp, PDUs: transaction.PDUS, } reqBody, err := json.Marshal(request) if err != nil { return } client := &http.Client{Timeout: 2 * time.Second} req, err := http.NewRequest(http.MethodPut, requestUrl, bytes.NewBuffer(reqBody)) if err != nil { return } if authentification { var authHeader string authHeader, err = utils.CreateAuthHeader(http.MethodPut, requestUrl, homeserver, string(reqBody)) if err != nil { return } req.Header["Authorization"] = []string{authHeader} } req.Header["Content-Type"] = []string{"application/json"} r, err := client.Do(req) if err != nil { return } if r.StatusCode != http.StatusOK { utils.HandleHTTPError(r) } else { buf := new(bytes.Buffer) buf.ReadFrom(r.Body) content := buf.String() if content == "" { err = errors.New("Missing Response") return } } return } func Backfill(eventIds []string, roomId string, homeserver string, httpString string, authentification bool) (err error) { requestUrl := fmt.Sprintf("%s://%s/_matrix/federation/v1/backfill/%s?", httpString, homeserver, roomId) for _, eventId := range eventIds { requestUrl = fmt.Sprintf("%sv=%s&", requestUrl, eventId) } client := &http.Client{Timeout: 2 * time.Second} var req *http.Request req, err = http.NewRequest(http.MethodGet, requestUrl, bytes.NewBuffer(nil)) if err != nil { return } if authentification { var authHeader string authHeader, err = utils.CreateAuthHeader(http.MethodGet, requestUrl, homeserver, "") if err != nil { return } req.Header["Authorization"] = []string{authHeader} } req.Header["Content-Type"] = []string{"application/json"} r, err := client.Do(req) if err != nil { return } if r.StatusCode != http.StatusOK { errResponse := utils.HandleHTTPError(r) log.Fatalf("%s (%s)", errResponse.ErrorMessage, errResponse.ErrorCode) } response := backfillResponse{} defer r.Body.Close() decoder := json.NewDecoder(r.Body) err = decoder.Decode(&response) if err != nil { return } var missingEventIds []string for _, pdu := range response.PDUs { for i, eventId := range missingEventIds { if pdu.Id == eventId { missingEventIds = append(missingEventIds[:i], missingEventIds[i+1:]...) } } signatureValid := CheckSignature(*pdu) if !signatureValid { log.Printf("Wrong Signature for Event %s", pdu.Id) missingEventIds = append(missingEventIds, pdu.Id) continue } authEventsValid, err := CheckAuthEvents(pdu) if !authEventsValid || err != nil { log.Printf("Wrong Auth Events for Event %s", pdu.Id) missingEventIds = append(missingEventIds, pdu.Id) } missingParentIds, err := CheckParents(pdu) if len(missingParentIds) > 0 || err != nil { for _, parentId := range missingParentIds { missingEventIds = append(missingEventIds, parentId) } } err = HandleEvent(pdu, "") if err != nil { log.Printf("Error in Event-Handling: %s", err) continue } } if len(missingEventIds) > 0 { Backfill(missingEventIds, roomId, homeserver, config.HttpString, config.AuthentificationCheck) } return } func generateEventId(id string) string { return fmt.Sprintf("$%s:%s", id, config.Homeserver) } func GetAuthChain(newEvent *Event) (authChain []*Event, err error) { if !config.AuthentificationCheck { return } createEvent, err := ReadStateEvent(newEvent.RoomId, "m.room.create", "") if err != nil { return } if createEvent != nil { authChain = append(authChain, createEvent) } powerLevelEvent, err := ReadStateEvent(newEvent.RoomId, "m.room.power_levels", "") if err != nil { return } if powerLevelEvent != nil { authChain = append(authChain, powerLevelEvent) } stateKey := newEvent.Sender if newEvent.EventType == "m.room.member" { stateKey = newEvent.StateKey } memberEvent, err := ReadStateEvent(newEvent.RoomId, "m.room.member", stateKey) if err != nil { return } if memberEvent != nil { authChain = append(authChain, memberEvent) } joinRuleEvent, err := ReadStateEvent(newEvent.RoomId, "m.room.join_rules", "") if err != nil { return } if joinRuleEvent != nil && newEvent.EventType == "m.room.member" { authChain = append(authChain, joinRuleEvent) } return } func GetAuthEvents(newEvent *Event) (authEventHashes map[string]EventHash, err error) { authEventHashes = make(map[string]EventHash) authChain, err := GetAuthChain(newEvent) if err != nil { return } for _, authEvent := range authChain { authEventHashes[authEvent.Id] = authEvent.Hashes } return } func CheckEventHash(id string, hash EventHash) (correct bool, eventFound bool, err error) { foundEvent, err := ReadEvent(id) correct = true eventFound = true if err != nil { return } if foundEvent == nil { eventFound = false return } if hash.SHA256 != foundEvent.Hashes.SHA256 { correct = false return } return } func CheckParents(eventToCheck *Event) (missingParentIds []string, err error) { if config.Consensus { for key, hash := range eventToCheck.PrevEventHashes { correctHash, foundEvent, err := CheckEventHash(key, hash) if !correctHash || !foundEvent || err != nil { missingParentIds = append(missingParentIds, key) } } } return } func CheckAuthEvents(eventToCheck *Event) (correct bool, err error) { correct = true if !config.AuthentificationCheck { return } authEvents, err := GetAuthEvents(eventToCheck) if err != nil { return } for key, hash := range authEvents { if eventToCheck.AuthEventHashes[key].SHA256 != hash.SHA256 { //correct = false return } } return } func CheckSignature(eventToCheck Event) (correct bool) { if !config.Signing { correct = true return } correct = false signatures := eventToCheck.Signatures eventToCheck.Unsigned = UnsignedData{} eventToCheck.Signatures = nil jsonString, err := json.Marshal(eventToCheck) if err != nil { return } for id, signature := range signatures[eventToCheck.Origin] { verifyKey, err := device.GetVerifyKey(eventToCheck.Origin, id) if err == nil && verifyKey != nil { correct = utils.VerifySignature(verifyKey, jsonString, signature) } } return } func HandleEvents(events []*Event, txnId string) (err error) { for _, eventToHandle := range events { err = HandleEvent(eventToHandle, txnId) } return } func HandleEvent(eventToHandle *Event, txnId string) (err error) { log.Printf("EventType: %s", eventToHandle.EventType) foundEvent, err := ReadEvent(eventToHandle.Id) if foundEvent == nil && err == nil { err = CreateEvent(eventToHandle, txnId) } if err != nil { return } if eventToHandle.EventType == "m.room.message" { log.Printf("%s: %s", eventToHandle.Sender, eventToHandle.Content) } else if eventToHandle.EventType == "m.room.member" { message := MemberEventContent{} err = json.Unmarshal([]byte(eventToHandle.Content), &message) if err != nil { return } if message.Membership == "join" { log.Printf("Join %s to Room %s", eventToHandle.StateKey, eventToHandle.RoomId) } err = CreateRoomMember(eventToHandle.RoomId, eventToHandle.StateKey, eventToHandle.Origin) if err != nil { return } } return }