├── pkg ├── mumbleproto │ ├── generate.go │ ├── generate_main.go │ └── types.go ├── cryptstate │ ├── testgen │ │ ├── README │ │ ├── test.cpp │ │ ├── test3.cpp │ │ ├── test2.cpp │ │ ├── CryptState.h │ │ └── CryptState.cpp │ ├── mode_null.go │ ├── mode_ocb2.go │ ├── mode_secretbox.go │ ├── cryptstate_test.go │ ├── cryptstate.go │ └── ocb2 │ │ ├── ocb2_test.go │ │ └── ocb2.go ├── replacefile │ ├── doc.go │ ├── flags.go │ ├── replacefile_stub.go │ └── replacefile_windows.go ├── freezer │ ├── types.go │ ├── error.go │ ├── types.proto │ ├── writer.go │ ├── walker.go │ ├── freezer_test.go │ └── types.pb.go ├── acl │ ├── interfaces.go │ ├── context.go │ ├── acl.go │ └── group.go ├── serverconf │ ├── config_test.go │ └── config.go ├── sessionpool │ ├── sessionpool_test.go │ └── sessionpool.go ├── blobstore │ ├── blobreader_test.go │ ├── blobstore_test.go │ ├── blobreader.go │ └── blobstore.go ├── web │ ├── websocket.go │ └── wslisten.go ├── ban │ ├── ban.go │ └── ban_test.go ├── logtarget │ └── logtarget.go ├── packetdata │ ├── packetdata_test.go │ └── packetdata.go └── htmlfilter │ └── htmlfilter.go ├── cmd └── grumble │ ├── version.go │ ├── signal_windows.go │ ├── signal_unix.go │ ├── freeze_unix.go │ ├── freeze_windows.go │ ├── user.go │ ├── gencert.go │ ├── args.go │ ├── channel.go │ ├── register.go │ ├── voicetarget.go │ ├── grumble.go │ └── murmurdb.go ├── AUTHORS ├── go.mod ├── .gitignore ├── .github └── workflows │ └── go.yml ├── Dockerfile ├── Dockerfile.arm32v6 ├── .goreleaser.yml ├── LICENSE ├── README.md └── go.sum /pkg/mumbleproto/generate.go: -------------------------------------------------------------------------------- 1 | //go:generate go run generate_main.go 2 | 3 | package mumbleproto 4 | -------------------------------------------------------------------------------- /pkg/cryptstate/testgen/README: -------------------------------------------------------------------------------- 1 | C++ code for generating some of the test vectors used in cryptstate_test.go 2 | -------------------------------------------------------------------------------- /cmd/grumble/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var ( 4 | version = "1.0~devel" 5 | buildDate = "unknown" 6 | ) 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Benjamin Jemlich 2 | Mikkel Krautz 3 | Tim Cooper 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mumble.info/grumble 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.4 7 | github.com/gorilla/websocket v1.5.1 8 | golang.org/x/crypto v0.21.0 9 | golang.org/x/net v0.22.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /cmd/grumble/signal_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | func SignalHandler() { 8 | } 9 | -------------------------------------------------------------------------------- /pkg/replacefile/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // Package replacefile implements access to the ReplaceFile Win32 API. 6 | package replacefile 7 | -------------------------------------------------------------------------------- /pkg/replacefile/flags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package replacefile 6 | 7 | type Flag uint32 8 | 9 | const ( 10 | IgnoreMergeErrors Flag = 0x2 11 | IgnoreACLErrors Flag = 0x4 12 | ) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/grumble/grumble 2 | .DS_Store 3 | *.[568ao] 4 | *.ao 5 | *.so 6 | *.pyc 7 | ._* 8 | .nfs.* 9 | [568a].out 10 | *~ 11 | *.orig 12 | *.rej 13 | *.exe 14 | .*.swp 15 | core 16 | *.cgo*.go 17 | *.cgo*.c 18 | _cgo_* 19 | _obj 20 | _test 21 | _testmain.go 22 | build.out 23 | test.out 24 | goinstall.log 25 | *.sqlite 26 | build/ 27 | .idea/ -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 2 | name: Go 3 | on: [push, pull_request] 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Build 10 | run: go build -v ./... 11 | - name: Test 12 | run: go test -v ./... 13 | -------------------------------------------------------------------------------- /pkg/freezer/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package freezer 6 | 7 | type typeKind uint32 8 | 9 | const ( 10 | ServerType typeKind = iota 11 | ConfigKeyValuePairType 12 | BanListType 13 | UserType 14 | UserRemoveType 15 | ChannelType 16 | ChannelRemoveType 17 | ) 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14-alpine as builder 2 | 3 | COPY . /go/src/mumble.info/grumble 4 | 5 | WORKDIR /go/src/mumble.info/grumble 6 | 7 | RUN apk add --no-cache git build-base 8 | 9 | RUN go get -v -t ./... \ 10 | && go build mumble.info/grumble/cmd/grumble \ 11 | && go test -v ./... 12 | 13 | FROM alpine:edge 14 | 15 | COPY --from=builder /go/bin/grumble /usr/bin/grumble 16 | 17 | ENV DATADIR /data 18 | 19 | RUN mkdir /data 20 | 21 | WORKDIR /data 22 | 23 | VOLUME /data 24 | 25 | EXPOSE 64738/tcp 26 | EXPOSE 64738/udp 27 | 28 | ENTRYPOINT [ "/usr/bin/grumble", "--datadir", "/data", "--log", "/data/grumble.log" ] 29 | -------------------------------------------------------------------------------- /Dockerfile.arm32v6: -------------------------------------------------------------------------------- 1 | FROM arm32v6/golang:1.14-alpine as builder 2 | 3 | COPY . /go/src/mumble.info/grumble 4 | 5 | WORKDIR /go/src/mumble.info/grumble 6 | 7 | RUN apk add --no-cache git build-base 8 | 9 | RUN go get -v -t ./... \ 10 | && go build mumble.info/grumble/cmd/grumble \ 11 | && go test -v ./... 12 | 13 | FROM arm32v6/alpine:edge 14 | 15 | COPY --from=builder /go/bin/grumble /usr/bin/grumble 16 | 17 | ENV DATADIR /data 18 | 19 | RUN mkdir /data 20 | 21 | WORKDIR /data 22 | 23 | VOLUME /data 24 | 25 | EXPOSE 64738/tcp 26 | EXPOSE 64738/udp 27 | 28 | ENTRYPOINT [ "/usr/bin/grumble", "--datadir", "/data", "--log", "/data/grumble.log" ] 29 | -------------------------------------------------------------------------------- /pkg/acl/interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package acl 6 | 7 | // User represents a user on a Mumble server. 8 | // The User interface represents the method set that 9 | // must be implemented in order to check a user's 10 | // permissions in an ACL context. 11 | type User interface { 12 | Session() uint32 13 | UserId() int 14 | 15 | CertHash() string 16 | Tokens() []string 17 | ACLContext() *Context 18 | } 19 | 20 | // Channel represents a Channel on a Mumble server. 21 | type Channel interface { 22 | ChannelId() int 23 | } 24 | -------------------------------------------------------------------------------- /pkg/replacefile/replacefile_stub.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // +build !windows 6 | 7 | package replacefile 8 | 9 | import ( 10 | "errors" 11 | ) 12 | 13 | var ( 14 | errOnlyWindows = errors.New("replacefile: only implemented on Windows") 15 | ErrUnableToMoveReplacement error = errOnlyWindows 16 | ErrUnableToMoveReplacement2 error = errOnlyWindows 17 | ErrUnableToRemoveReplaced error = errOnlyWindows 18 | ) 19 | 20 | func ReplaceFile(replaced string, replacement string, backup string, flags Flag) error { 21 | return errOnlyWindows 22 | } 23 | -------------------------------------------------------------------------------- /pkg/freezer/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package freezer 6 | 7 | import "errors" 8 | 9 | // Writer errors 10 | var ( 11 | ErrTxGroupFull = errors.New("transction group is full") 12 | ErrTxGroupValueTooBig = errors.New("value too big to put inside the txgroup") 13 | ) 14 | 15 | // Walker errors 16 | var ( 17 | ErrUnexpectedEndOfRecord = errors.New("unexpected end of record") 18 | ErrCRC32Mismatch = errors.New("CRC32 mismatch") 19 | ErrRemainingBytesForRecord = errors.New("remaining bytes in record") 20 | ErrRecordTooBig = errors.New("the record in the file is too big") 21 | ) 22 | -------------------------------------------------------------------------------- /pkg/cryptstate/testgen/test.cpp: -------------------------------------------------------------------------------- 1 | #include "CryptState.h" 2 | #include 3 | 4 | unsigned char msg[] = { 5 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 6 | }; 7 | 8 | static void DumpBytes(unsigned char *bytes, unsigned int len, const char *name) { 9 | printf("unsigned char %s[] = { ", name); 10 | for (int i = 0; i < len; i++) { 11 | printf("0x%.2x, ", bytes[i]); 12 | } 13 | printf("}\n"); 14 | } 15 | 16 | int main(int argc, char *argv[]) { 17 | MumbleClient::CryptState cs; 18 | cs.genKey(); 19 | 20 | DumpBytes(cs.raw_key, AES_BLOCK_SIZE, "rawkey"); 21 | DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "encrypt_iv"); 22 | DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "decrypt_iv"); 23 | 24 | unsigned char buf[19]; 25 | cs.encrypt(msg, &buf[0], 15); 26 | 27 | DumpBytes(buf, 19, "crypted"); 28 | } 29 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: grumble 2 | dist: build/ 3 | env: 4 | - GO111MODULE=on 5 | before: 6 | hooks: 7 | - go mod download 8 | builds: 9 | - env: 10 | - CGO_ENABLED=0 11 | main: ./cmd/grumble/ 12 | binary: grumble 13 | goos: 14 | - darwin 15 | - linux 16 | - windows 17 | goarch: 18 | - amd64 19 | - arm 20 | - arm64 21 | ignore: 22 | - goos: darwin 23 | goarch: arm 24 | - goos: darwin 25 | goarch: arm64 26 | - goos: windows 27 | goarch: arm 28 | - goos: windows 29 | goarch: arm64 30 | archives: 31 | - files: 32 | - LICENSE 33 | format_overrides: 34 | - goos: windows 35 | format: zip 36 | checksum: 37 | name_template: 'checksums.txt' 38 | snapshot: 39 | name_template: "{{ .Tag }}-next" 40 | changelog: 41 | sort: asc 42 | filters: 43 | exclude: 44 | - '^docs:' 45 | - '^test:' 46 | -------------------------------------------------------------------------------- /pkg/serverconf/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package serverconf 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestIntValue(t *testing.T) { 12 | cfg := New(nil) 13 | cfg.Set("Test", "13") 14 | if cfg.IntValue("Test") != 13 { 15 | t.Errorf("Expected 13") 16 | } 17 | } 18 | 19 | func TestFloatAsInt(t *testing.T) { 20 | cfg := New(nil) 21 | cfg.Set("Test", "13.4") 22 | if cfg.IntValue("Test") != 0 { 23 | t.Errorf("Expected 0") 24 | } 25 | } 26 | 27 | func TestDefaultValue(t *testing.T) { 28 | cfg := New(nil) 29 | if cfg.IntValue("MaxBandwidth") != 72000 { 30 | t.Errorf("Expected 72000") 31 | } 32 | } 33 | 34 | func TestBoolValue(t *testing.T) { 35 | cfg := New(nil) 36 | cfg.Set("DoStuffOnStartup", "true") 37 | if cfg.BoolValue("DoStuffOnStartup") != true { 38 | t.Errorf("Expected true") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/sessionpool/sessionpool_test.go: -------------------------------------------------------------------------------- 1 | package sessionpool 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestReclaim(t *testing.T) { 9 | pool := New() 10 | id := pool.Get() 11 | if id != 1 { 12 | t.Errorf("Got %v, expected 1 (first time)", id) 13 | } 14 | 15 | pool.Reclaim(1) 16 | 17 | id = pool.Get() 18 | if id != 1 { 19 | t.Errorf("Got %v, expected 1 (second time)", id) 20 | } 21 | 22 | id = pool.Get() 23 | if id != 2 { 24 | t.Errorf("Got %v, expected 2", id) 25 | } 26 | } 27 | 28 | func TestDepletion(t *testing.T) { 29 | defer func() { 30 | r := recover() 31 | if r != "SessionPool depleted" { 32 | t.Errorf("Expected depletion panic") 33 | } 34 | }() 35 | pool := New() 36 | pool.cur = math.MaxUint32 37 | pool.Get() 38 | } 39 | 40 | func TestUseTracking(t *testing.T) { 41 | defer func() { 42 | r := recover() 43 | if r != "Attempt to reclaim invalid session ID" { 44 | t.Errorf("Expected reclamation panic") 45 | } 46 | }() 47 | 48 | pool := New() 49 | pool.EnableUseTracking() 50 | pool.Reclaim(42) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/grumble/signal_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // +build darwin freebsd linux netbsd openbsd 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | 16 | "mumble.info/grumble/pkg/logtarget" 17 | ) 18 | 19 | func SignalHandler() { 20 | sigchan := make(chan os.Signal, 10) 21 | signal.Notify(sigchan, syscall.SIGUSR2, syscall.SIGTERM, syscall.SIGINT) 22 | for sig := range sigchan { 23 | if sig == syscall.SIGUSR2 { 24 | err := logtarget.Default.Rotate() 25 | if err != nil { 26 | fmt.Fprintf(os.Stderr, "unable to rotate log file: %v", err) 27 | } 28 | continue 29 | } 30 | if sig == syscall.SIGINT || sig == syscall.SIGTERM { 31 | for _, server := range servers { 32 | log.Printf("Stopping server %v", server.Id) 33 | err := server.Stop() 34 | if err != nil { 35 | log.Printf("Server err %v", err) 36 | } 37 | } 38 | log.Print("All servers stopped. Exiting.") 39 | os.Exit(0) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/cryptstate/mode_null.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package cryptstate 6 | 7 | // nullMode implements the NULL CryptoMode 8 | type nullMode struct{} 9 | 10 | // NonceSize returns the nonce size to be used with NULL. 11 | func (n *nullMode) NonceSize() int { 12 | return 1 13 | } 14 | 15 | // KeySize returns the key size to be used with NULL. 16 | func (n *nullMode) KeySize() int { 17 | return 0 18 | } 19 | 20 | // Overhead returns the overhead that a ciphertext has over a plaintext. 21 | func (n *nullMode) Overhead() int { 22 | return 0 23 | } 24 | 25 | // SetKey sets a new key. The key must have a length equal to KeySize(). 26 | func (n *nullMode) SetKey(key []byte) { 27 | } 28 | 29 | // Encrypt encrypts a message using NULL and outputs it to dst. 30 | func (n *nullMode) Encrypt(dst []byte, src []byte, nonce []byte) { 31 | copy(dst, src) 32 | } 33 | 34 | // Decrypt decrypts a message using NULL and outputs it to dst. 35 | func (n *nullMode) Decrypt(dst []byte, src []byte, nonce []byte) bool { 36 | copy(dst, src) 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /pkg/blobstore/blobreader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package blobstore 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | "io/ioutil" 11 | "testing" 12 | ) 13 | 14 | type blobReaderTest struct { 15 | Key string 16 | ExpectedSum string 17 | Data string 18 | } 19 | 20 | var blobReaderTests = []blobReaderTest{ 21 | { 22 | Key: "a3da7877f94ad4cf58636a395fff77537cb8b919", 23 | ExpectedSum: "a3da7877f94ad4cf58636a395fff77537cb8b919", 24 | Data: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 25 | }, 26 | } 27 | 28 | func TestBlobReader(t *testing.T) { 29 | for _, test := range blobReaderTests { 30 | rc := ioutil.NopCloser(bytes.NewBufferString(test.Data)) 31 | br, err := newBlobReader(rc, test.Key) 32 | if err != nil { 33 | t.Errorf("unable to construct blob reader: %v", err) 34 | continue 35 | } 36 | _, err = io.Copy(ioutil.Discard, br) 37 | if err != nil { 38 | t.Errorf("got error: %v", err) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/mumbleproto/generate_main.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os/exec" 10 | "regexp" 11 | ) 12 | 13 | var replacements = []string{ 14 | `(?m)^package MumbleProto;$`, `package mumbleproto;`, 15 | 16 | // Add crypto_modes to Version message. 17 | // It is only present in Grumble, not in upstream Murmur. 18 | `(?m)^(message Version {)$`, "$1\n\trepeated string crypto_modes = 5;\n", 19 | } 20 | 21 | func main() { 22 | // Fetch Mumble.proto 23 | resp, err := http.Get("https://raw.githubusercontent.com/mumble-voip/mumble/master/src/Mumble.proto") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | defer resp.Body.Close() 28 | data, err := ioutil.ReadAll(resp.Body) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | // Perform replacements 34 | for i := 0; i < len(replacements); i += 2 { 35 | re, rp := replacements[i], replacements[i+1] 36 | regex, err := regexp.Compile(re) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | data = regex.ReplaceAll(data, []byte(rp)) 41 | } 42 | 43 | // Write Mumble.proto 44 | if err := ioutil.WriteFile("Mumble.proto", data, 0644); err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | // Run protobuf compiler 49 | if err := exec.Command("protoc", "--go_out=.", "Mumble.proto").Run(); err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/grumble/freeze_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // +build !windows 6 | 7 | package main 8 | 9 | import ( 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | 15 | "github.com/golang/protobuf/proto" 16 | ) 17 | 18 | func (server *Server) freezeToFile() (err error) { 19 | // Close the log file, if it's open 20 | if server.freezelog != nil { 21 | err = server.freezelog.Close() 22 | if err != nil { 23 | return err 24 | } 25 | server.freezelog = nil 26 | } 27 | 28 | // Make sure the whole server is synced to disk 29 | fs, err := server.Freeze() 30 | if err != nil { 31 | return err 32 | } 33 | f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_") 34 | if err != nil { 35 | return err 36 | } 37 | buf, err := proto.Marshal(fs) 38 | if err != nil { 39 | return err 40 | } 41 | _, err = f.Write(buf) 42 | if err != nil { 43 | return err 44 | } 45 | err = f.Sync() 46 | if err != nil { 47 | return err 48 | } 49 | err = f.Close() 50 | if err != nil { 51 | return err 52 | } 53 | err = os.Rename(f.Name(), filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz")) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/cryptstate/testgen/test3.cpp: -------------------------------------------------------------------------------- 1 | #include "CryptState.h" 2 | #include 3 | 4 | unsigned char msg[] = { 5 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 6 | }; 7 | 8 | unsigned char rawkey[] = { 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, }; 9 | unsigned char encrypt_iv[] = { 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, }; 10 | unsigned char decrypt_iv[] = { 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, }; 11 | unsigned char crypted[] = { 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, }; 12 | 13 | static void DumpBytes(unsigned char *bytes, unsigned int len, const char *name) { 14 | printf("unsigned char %s[] = { ", name); 15 | for (int i = 0; i < len; i++) { 16 | printf("0x%.2x, ", bytes[i]); 17 | } 18 | printf("}\n"); 19 | } 20 | 21 | int main(int argc, char *argv[]) { 22 | MumbleClient::CryptState cs; 23 | cs.setKey(rawkey, decrypt_iv, encrypt_iv); 24 | 25 | DumpBytes(cs.raw_key, AES_BLOCK_SIZE, "rawkey"); 26 | DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "encrypt_iv"); 27 | DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "decrypt_iv"); 28 | 29 | unsigned char buf[15]; 30 | cs.decrypt(crypted, &buf[0], 19); 31 | 32 | DumpBytes(buf, 15, "plain"); 33 | DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "post_div"); 34 | } 35 | -------------------------------------------------------------------------------- /pkg/cryptstate/testgen/test2.cpp: -------------------------------------------------------------------------------- 1 | #include "CryptState.h" 2 | #include 3 | 4 | unsigned char msg[] = { 5 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 6 | }; 7 | 8 | unsigned char rawkey[] = { 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, }; 9 | unsigned char encrypt_iv[] = { 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, }; 10 | unsigned char decrypt_iv[] = { 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, }; 11 | unsigned char crypted[] = { 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, }; 12 | 13 | 14 | static void DumpBytes(unsigned char *bytes, unsigned int len, const char *name) { 15 | printf("unsigned char %s[] = { ", name); 16 | for (int i = 0; i < len; i++) { 17 | printf("0x%.2x, ", bytes[i]); 18 | } 19 | printf("}\n"); 20 | } 21 | 22 | int main(int argc, char *argv[]) { 23 | MumbleClient::CryptState cs; 24 | // cs.genKey(); 25 | cs.setKey(rawkey, encrypt_iv, decrypt_iv); 26 | 27 | DumpBytes(cs.raw_key, AES_BLOCK_SIZE, "rawkey"); 28 | DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "encrypt_iv"); 29 | DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "decrypt_iv"); 30 | 31 | unsigned char buf[19]; 32 | cs.encrypt(msg, &buf[0], 15); 33 | 34 | DumpBytes(buf, 19, "crypted"); 35 | DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "post_eiv"); 36 | } 37 | -------------------------------------------------------------------------------- /pkg/acl/context.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2013 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package acl 6 | 7 | // Context represents a context in which ACLs can 8 | // be understood. Typically embedded into a type 9 | // that represents a Mumble channel. 10 | type Context struct { 11 | // Parent points to the context's parent. 12 | // May be nil if the Context does not have a parent. 13 | Parent *Context 14 | 15 | // ACLs is the Context's list of ACL entries. 16 | ACLs []ACL 17 | 18 | // Groups is the Context's representation of groups. 19 | // It is indexed by the Group's name. 20 | Groups map[string]Group 21 | 22 | // InheritACL determines whether this context should 23 | // inherit ACLs from its parent. 24 | InheritACL bool 25 | } 26 | 27 | // indexOf finds the index of the context ctx in the context chain contexts. 28 | // Returns -1 if the given context was not found in the context chain. 29 | func indexOf(contexts []*Context, ctx *Context) int { 30 | for i, iter := range contexts { 31 | if iter == ctx { 32 | return i 33 | } 34 | } 35 | return -1 36 | } 37 | 38 | // buildChain walks from the context ctx back through all of its parents, 39 | // collecting them all in a slice. The first element of the returned 40 | // slice is the final ancestor (it has a nil Parent). 41 | func buildChain(ctx *Context) []*Context { 42 | chain := []*Context{} 43 | for ctx != nil { 44 | chain = append([]*Context{ctx}, chain...) 45 | ctx = ctx.Parent 46 | } 47 | return chain 48 | } 49 | -------------------------------------------------------------------------------- /pkg/web/websocket.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The Grumble Authors 2 | // The use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package web 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | "net" 11 | "time" 12 | 13 | "github.com/gorilla/websocket" 14 | ) 15 | 16 | type conn struct { 17 | ws *websocket.Conn 18 | msgbuf bytes.Buffer 19 | } 20 | 21 | func (c *conn) Read(b []byte) (n int, err error) { 22 | if c.msgbuf.Len() == 0 { 23 | _, r, err := c.ws.NextReader() 24 | if err != nil { 25 | if _, ok := err.(*websocket.CloseError); ok { 26 | return 0, io.EOF 27 | } 28 | return 0, err 29 | } 30 | if _, err := c.msgbuf.ReadFrom(r); err != nil { 31 | return 0, err 32 | } 33 | } 34 | // Impossible to read over message boundaries - will generate EOF 35 | return c.msgbuf.Read(b) 36 | } 37 | 38 | func (c *conn) Write(b []byte) (n int, err error) { 39 | return len(b), c.ws.WriteMessage(websocket.BinaryMessage, b) 40 | } 41 | 42 | func (c *conn) Close() error { 43 | return c.ws.Close() 44 | } 45 | 46 | func (c *conn) LocalAddr() net.Addr { 47 | return c.ws.LocalAddr() 48 | } 49 | 50 | func (c *conn) RemoteAddr() net.Addr { 51 | return c.ws.RemoteAddr() 52 | } 53 | 54 | func (c *conn) SetDeadline(t time.Time) (err error) { 55 | if err = c.ws.SetReadDeadline(t); err != nil { 56 | return err 57 | } 58 | return c.ws.SetWriteDeadline(t) 59 | } 60 | 61 | func (c *conn) SetReadDeadline(t time.Time) error { 62 | return c.ws.SetReadDeadline(t) 63 | } 64 | 65 | func (c *conn) SetWriteDeadline(t time.Time) error { 66 | return c.ws.SetWriteDeadline(t) 67 | } 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Grumble - an implementation of Murmur in Go 2 | 3 | Copyright (c) 2010-2020 The Grumble Authors 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | - Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | - Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | - Neither the name of the Mumble Developers nor the names of its 17 | contributors may be used to endorse or promote products derived from this 18 | software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /cmd/grumble/freeze_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | import ( 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | 13 | "github.com/golang/protobuf/proto" 14 | "mumble.info/grumble/pkg/replacefile" 15 | ) 16 | 17 | func (server *Server) freezeToFile() (err error) { 18 | // Close the log file, if it's open 19 | if server.freezelog != nil { 20 | err = server.freezelog.Close() 21 | if err != nil { 22 | return err 23 | } 24 | server.freezelog = nil 25 | } 26 | 27 | // Make sure the whole server is synced to disk 28 | fs, err := server.Freeze() 29 | if err != nil { 30 | return err 31 | } 32 | f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_") 33 | if err != nil { 34 | return err 35 | } 36 | buf, err := proto.Marshal(fs) 37 | if err != nil { 38 | return err 39 | } 40 | _, err = f.Write(buf) 41 | if err != nil { 42 | return err 43 | } 44 | err = f.Sync() 45 | if err != nil { 46 | return err 47 | } 48 | err = f.Close() 49 | if err != nil { 50 | return err 51 | } 52 | 53 | src := f.Name() 54 | dst := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz") 55 | backup := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "backup.fz") 56 | 57 | err = replacefile.ReplaceFile(dst, src, backup, replacefile.Flag(0)) 58 | // If the dst file does not exist (as in, on first launch) 59 | // fall back to os.Rename. ReplaceFile does not work if the 60 | // dst file is not there. 61 | if os.IsNotExist(err) { 62 | err = os.Rename(src, dst) 63 | if err != nil { 64 | return err 65 | } 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/freezer/types.proto: -------------------------------------------------------------------------------- 1 | package freezer; 2 | 3 | option optimize_for = SPEED; 4 | 5 | message Server { 6 | repeated ConfigKeyValuePair config = 2; 7 | optional BanList ban_list = 3; 8 | repeated Channel channels = 4; 9 | repeated User users = 5; 10 | } 11 | 12 | message ConfigKeyValuePair { 13 | required string key = 1; 14 | optional string value = 2; 15 | } 16 | 17 | message Ban { 18 | optional bytes ip = 1; 19 | optional uint32 mask = 2; 20 | optional string username = 3; 21 | optional string cert_hash = 4; 22 | optional string reason = 5; 23 | optional int64 start = 6; 24 | optional uint32 duration = 7; 25 | } 26 | 27 | message BanList { 28 | repeated Ban bans = 1; 29 | } 30 | 31 | message User { 32 | optional uint32 id = 1; 33 | optional string name = 2; 34 | optional string password = 3; 35 | optional string cert_hash = 4; 36 | optional string email = 5; 37 | optional string texture_blob = 6; 38 | optional string comment_blob = 7; 39 | optional uint32 last_channel_id = 8; 40 | optional uint64 last_active = 9; 41 | } 42 | 43 | message UserRemove { 44 | optional uint32 id = 1; 45 | } 46 | 47 | message Channel { 48 | optional uint32 id = 1; 49 | optional string name = 2; 50 | optional uint32 parent_id = 3; 51 | optional int64 position = 4; 52 | optional bool inherit_acl = 5; 53 | repeated uint32 links = 6; 54 | repeated ACL acl = 7; 55 | repeated Group groups = 8; 56 | optional string description_blob = 9; 57 | } 58 | 59 | message ChannelRemove { 60 | optional uint32 id = 1; 61 | } 62 | 63 | message ACL { 64 | optional uint32 user_id = 1; 65 | optional string group = 2; 66 | optional bool apply_here = 3; 67 | optional bool apply_subs = 4; 68 | optional uint32 allow = 5; 69 | optional uint32 deny = 6; 70 | } 71 | 72 | message Group { 73 | optional string name = 1; 74 | optional bool inherit = 2; 75 | optional bool inheritable = 3; 76 | repeated uint32 add = 4; 77 | repeated uint32 remove = 5; 78 | } -------------------------------------------------------------------------------- /cmd/grumble/user.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/hex" 9 | "errors" 10 | ) 11 | 12 | // This file implements Server's handling of Users. 13 | // 14 | // Users are registered clients on the server. 15 | 16 | type User struct { 17 | Id uint32 18 | Name string 19 | Password string 20 | CertHash string 21 | Email string 22 | TextureBlob string 23 | CommentBlob string 24 | LastChannelId int 25 | LastActive uint64 26 | } 27 | 28 | // Create a new User 29 | func NewUser(id uint32, name string) (user *User, err error) { 30 | if id < 0 { 31 | return nil, errors.New("Invalid user id") 32 | } 33 | if len(name) == 0 { 34 | return nil, errors.New("Invalid username") 35 | } 36 | 37 | return &User{ 38 | Id: id, 39 | Name: name, 40 | }, nil 41 | } 42 | 43 | // HasComment Does the channel have comment? 44 | func (user *User) HasComment() bool { 45 | return len(user.CommentBlob) > 0 46 | } 47 | 48 | // CommentBlobHashBytes gets the hash of the user's comment blob as a byte slice for transmitting via a protobuf message. 49 | // Returns nil if there is no such blob. 50 | func (user *User) CommentBlobHashBytes() (buf []byte) { 51 | buf, err := hex.DecodeString(user.CommentBlob) 52 | if err != nil { 53 | return nil 54 | } 55 | return buf 56 | } 57 | 58 | // HasTexture Does the user have a texture? 59 | func (user *User) HasTexture() bool { 60 | return len(user.TextureBlob) > 0 61 | } 62 | 63 | // TextureBlobHashBytes gets the hash of the user's texture blob as a byte slice for transmitting via a protobuf message. 64 | // Returns nil if there is no such blob. 65 | func (user *User) TextureBlobHashBytes() (buf []byte) { 66 | buf, err := hex.DecodeString(user.TextureBlob) 67 | if err != nil { 68 | return nil 69 | } 70 | return buf 71 | } 72 | -------------------------------------------------------------------------------- /pkg/ban/ban.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package ban 6 | 7 | import ( 8 | "net" 9 | "time" 10 | ) 11 | 12 | const ( 13 | ISODate = "2006-01-02T15:04:05" 14 | ) 15 | 16 | type Ban struct { 17 | IP net.IP 18 | Mask int 19 | Username string 20 | CertHash string 21 | Reason string 22 | Start int64 23 | Duration uint32 24 | } 25 | 26 | // Create a net.IPMask from a specified amount of mask bits 27 | func (ban Ban) IPMask() (mask net.IPMask) { 28 | allbits := ban.Mask 29 | for i := 0; i < 16; i++ { 30 | bits := allbits 31 | if bits > 0 { 32 | if bits > 8 { 33 | bits = 8 34 | } 35 | mask = append(mask, byte((1< expiryTime { 78 | return true 79 | } 80 | return false 81 | } 82 | -------------------------------------------------------------------------------- /pkg/cryptstate/mode_ocb2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package cryptstate 6 | 7 | import ( 8 | "crypto/aes" 9 | "crypto/cipher" 10 | 11 | "mumble.info/grumble/pkg/cryptstate/ocb2" 12 | ) 13 | 14 | // ocb2Mode implements the OCB2-AES128 CryptoMode 15 | type ocb2Mode struct { 16 | cipher cipher.Block 17 | } 18 | 19 | // NonceSize returns the nonce size to be used with OCB2-AES128. 20 | func (ocb *ocb2Mode) NonceSize() int { 21 | return ocb2.NonceSize 22 | } 23 | 24 | // KeySize returns the key size to be used with OCB2-AES128. 25 | func (ocb *ocb2Mode) KeySize() int { 26 | return aes.BlockSize 27 | } 28 | 29 | // Overhead returns the overhead that a ciphertext has over a plaintext. 30 | // In the case of OCB2-AES128, the overhead is the authentication tag. 31 | func (ocb *ocb2Mode) Overhead() int { 32 | return 3 33 | } 34 | 35 | // SetKey sets a new key. The key must have a length equal to KeySize(). 36 | func (ocb *ocb2Mode) SetKey(key []byte) { 37 | if len(key) != ocb.KeySize() { 38 | panic("cryptstate: invalid key length") 39 | } 40 | 41 | cipher, err := aes.NewCipher(key) 42 | if err != nil { 43 | panic("cryptstate: NewCipher returned unexpected " + err.Error()) 44 | } 45 | ocb.cipher = cipher 46 | } 47 | 48 | // Encrypt encrypts a message using OCB2-AES128 and outputs it to dst. 49 | func (ocb *ocb2Mode) Encrypt(dst []byte, src []byte, nonce []byte) { 50 | if len(dst) <= ocb.Overhead() { 51 | panic("cryptstate: bad dst") 52 | } 53 | 54 | tag := dst[0:3] 55 | dst = dst[3:] 56 | ocb2.Encrypt(ocb.cipher, dst, src, nonce, tag) 57 | } 58 | 59 | // Decrypt decrypts a message using OCB2-AES128 and outputs it to dst. 60 | // Returns false if decryption failed (authentication tag mismatch). 61 | func (ocb *ocb2Mode) Decrypt(dst []byte, src []byte, nonce []byte) bool { 62 | if len(src) <= ocb.Overhead() { 63 | panic("cryptstate: bad src") 64 | } 65 | 66 | tag := src[0:3] 67 | src = src[3:] 68 | return ocb2.Decrypt(ocb.cipher, dst, src, nonce, tag) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/web/wslisten.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The Grumble Authors 2 | // The use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package web 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net" 11 | "net/http" 12 | "sync/atomic" 13 | "time" 14 | 15 | "github.com/gorilla/websocket" 16 | ) 17 | 18 | var upgrader = websocket.Upgrader{ 19 | HandshakeTimeout: 20 * time.Second, 20 | Subprotocols: []string{"mumble", "binary"}, 21 | CheckOrigin: func(r *http.Request) bool { 22 | return true 23 | }, 24 | } 25 | 26 | type Listener struct { 27 | sockets chan *conn 28 | done chan struct{} 29 | addr net.Addr 30 | closed int32 31 | logger *log.Logger 32 | } 33 | 34 | func NewListener(laddr net.Addr, logger *log.Logger) *Listener { 35 | return &Listener{ 36 | sockets: make(chan *conn), 37 | done: make(chan struct{}), 38 | addr: laddr, 39 | logger: logger, 40 | } 41 | } 42 | 43 | func (l *Listener) Accept() (net.Conn, error) { 44 | if atomic.LoadInt32(&l.closed) != 0 { 45 | return nil, fmt.Errorf("accept ws %v: use of closed websocket listener", l.addr) 46 | } 47 | select { 48 | case ws := <-l.sockets: 49 | return ws, nil 50 | case <-l.done: 51 | return nil, fmt.Errorf("accept ws %v: use of closed websocket listener", l.addr) 52 | } 53 | } 54 | 55 | func (l *Listener) Close() error { 56 | if !atomic.CompareAndSwapInt32(&l.closed, 0, 1) { 57 | return fmt.Errorf("close ws %v: use of closed websocket listener", l.addr) 58 | } 59 | close(l.done) 60 | return nil 61 | } 62 | 63 | func (l *Listener) Addr() net.Addr { 64 | return l.addr 65 | } 66 | 67 | func (l *Listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { 68 | if atomic.LoadInt32(&l.closed) != 0 { 69 | http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) 70 | return 71 | } 72 | l.logger.Printf("Upgrading web connection from: %v", r.RemoteAddr) 73 | ws, err := upgrader.Upgrade(w, r, nil) 74 | if err != nil { 75 | l.logger.Printf("Failed upgrade: %v", err) 76 | return 77 | } 78 | l.sockets <- &conn{ws: ws} 79 | } 80 | -------------------------------------------------------------------------------- /pkg/replacefile/replacefile_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package replacefile 6 | 7 | import ( 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | modkernel32 = syscall.NewLazyDLL("kernel32.dll") 14 | procReplaceFileW = modkernel32.NewProc("ReplaceFileW") 15 | ) 16 | 17 | // Define the syscall.Errno backed-errors here in order to get a cleaner 18 | // godoc output. 19 | var ( 20 | win32_ERROR_UNABLE_TO_MOVE_REPLACEMENT = syscall.Errno(0x498) 21 | win32_ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 = syscall.Errno(0x499) 22 | win32_ERROR_UNABLE_TO_REMOVE_REPLACED = syscall.Errno(0x497) 23 | ) 24 | 25 | var ( 26 | ErrUnableToMoveReplacement error = win32_ERROR_UNABLE_TO_MOVE_REPLACEMENT 27 | ErrUnableToMoveReplacement2 error = win32_ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 28 | ErrUnableToRemoveReplaced error = win32_ERROR_UNABLE_TO_REMOVE_REPLACED 29 | ) 30 | 31 | func replaceFileW(replaced *uint16, replacement *uint16, backup *uint16, flags uint32) (err error) { 32 | r1, _, e1 := syscall.Syscall6(procReplaceFileW.Addr(), 6, uintptr(unsafe.Pointer(replaced)), uintptr(unsafe.Pointer(replacement)), uintptr(unsafe.Pointer(backup)), uintptr(flags), 0, 0) 33 | if r1 == 0 { 34 | if e1 != 0 { 35 | err = error(e1) 36 | } else { 37 | err = syscall.EINVAL 38 | } 39 | } 40 | return 41 | } 42 | 43 | // ReplaceFile calls through to the Win32 ReplaceFile API, which can be found at the following 44 | // URL: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx 45 | func ReplaceFile(replaced string, replacement string, backup string, flags Flag) error { 46 | replacedPtr, err := syscall.UTF16PtrFromString(replaced) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | replacementPtr, err := syscall.UTF16PtrFromString(replacement) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | backupPtr, err := syscall.UTF16PtrFromString(backup) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return replaceFileW(replacedPtr, replacementPtr, backupPtr, uint32(flags)) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/cryptstate/mode_secretbox.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package cryptstate 6 | 7 | import ( 8 | "unsafe" 9 | 10 | "golang.org/x/crypto/nacl/secretbox" 11 | ) 12 | 13 | // secretBoxMode implements the XSalsa20-Poly1305 CryptoMode 14 | type secretBoxMode struct { 15 | key [32]byte 16 | } 17 | 18 | // NonceSize returns the nonce size to be used with XSalsa20-Poly1305. 19 | func (sb *secretBoxMode) NonceSize() int { 20 | return 24 21 | } 22 | 23 | // KeySize returns the key size to be used with XSalsa20-Poly1305. 24 | func (sb *secretBoxMode) KeySize() int { 25 | return 32 26 | } 27 | 28 | // Overhead returns the overhead that a ciphertext has over a plaintext. 29 | // In the case of XSalsa20-Poly1305 the overhead is the authentication tag. 30 | func (sb *secretBoxMode) Overhead() int { 31 | return secretbox.Overhead 32 | } 33 | 34 | // SetKey sets a new key. The key must have a length equal to KeySize(). 35 | func (sb *secretBoxMode) SetKey(key []byte) { 36 | if len(key) != sb.KeySize() { 37 | panic("cryptstate: invalid key length") 38 | } 39 | copy(sb.key[:], key) 40 | } 41 | 42 | // Encrypt encrypts a message using XSalsa20-Poly1305 and outputs it to dst. 43 | func (sb *secretBoxMode) Encrypt(dst []byte, src []byte, nonce []byte) { 44 | if len(dst) <= sb.Overhead() { 45 | panic("cryptstate: bad dst") 46 | } 47 | 48 | if len(nonce) != 24 { 49 | panic("cryptstate: bad nonce length") 50 | } 51 | 52 | noncePtr := (*[24]byte)(unsafe.Pointer(&nonce[0])) 53 | secretbox.Seal(dst[0:0], src, noncePtr, &sb.key) 54 | } 55 | 56 | // Decrypt decrypts a message using XSalsa20-Poly1305 and outputs it to dst. 57 | // Returns false if decryption failed (authentication tag mismatch). 58 | func (sb *secretBoxMode) Decrypt(dst []byte, src []byte, nonce []byte) bool { 59 | if len(src) <= sb.Overhead() { 60 | panic("cryptstate: bad src") 61 | } 62 | 63 | if len(nonce) != 24 { 64 | panic("cryptstate: bad nonce length") 65 | } 66 | 67 | noncePtr := (*[24]byte)(unsafe.Pointer(&nonce[0])) 68 | _, ok := secretbox.Open(dst[0:0], src, noncePtr, &sb.key) 69 | return ok 70 | } 71 | -------------------------------------------------------------------------------- /cmd/grumble/gencert.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/rsa" 10 | "crypto/x509" 11 | "crypto/x509/pkix" 12 | "encoding/pem" 13 | "log" 14 | "math/big" 15 | "os" 16 | "path/filepath" 17 | "time" 18 | ) 19 | 20 | // Generate a 4096-bit RSA keypair and a Grumble auto-generated X509 21 | // certificate. Output PEM-encoded DER representations of the resulting 22 | // certificate and private key to certpath and keypath. 23 | func GenerateSelfSignedCert(certpath, keypath string) (err error) { 24 | now := time.Now() 25 | tmpl := &x509.Certificate{ 26 | SerialNumber: big.NewInt(0), 27 | Subject: pkix.Name{ 28 | CommonName: "Grumble Autogenerated Certificate", 29 | }, 30 | NotBefore: now.Add(-300 * time.Second), 31 | // Valid for 1 year. 32 | NotAfter: now.Add(24 * time.Hour * 365), 33 | 34 | SubjectKeyId: []byte{1, 2, 3, 4}, 35 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 36 | } 37 | 38 | priv, err := rsa.GenerateKey(rand.Reader, 4096) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | certbuf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv) 44 | if err != nil { 45 | log.Printf("Error: %v", err) 46 | return err 47 | } 48 | certblk := pem.Block{ 49 | Type: "CERTIFICATE", 50 | Bytes: certbuf, 51 | } 52 | 53 | keybuf := x509.MarshalPKCS1PrivateKey(priv) 54 | keyblk := pem.Block{ 55 | Type: "RSA PRIVATE KEY", 56 | Bytes: keybuf, 57 | } 58 | 59 | certfn := filepath.Join(Args.DataDir, "cert.pem") 60 | file, err := os.OpenFile(certfn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) 61 | if err != nil { 62 | return err 63 | } 64 | defer file.Close() 65 | err = pem.Encode(file, &certblk) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | keyfn := filepath.Join(Args.DataDir, "key.pem") 71 | file, err = os.OpenFile(keyfn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) 72 | if err != nil { 73 | return err 74 | } 75 | defer file.Close() 76 | err = pem.Encode(file, &keyblk) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/blobstore/blobstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package blobstore 6 | 7 | import ( 8 | "bytes" 9 | "crypto/sha1" 10 | "encoding/hex" 11 | "io/ioutil" 12 | "os" 13 | "testing" 14 | ) 15 | 16 | func TestStoreRetrieve(t *testing.T) { 17 | dir, err := ioutil.TempDir("", "blobstore") 18 | if err != nil { 19 | t.Error(err) 20 | return 21 | } 22 | defer os.RemoveAll(dir) 23 | 24 | bs := Open(dir) 25 | 26 | data := []byte{0xde, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xbe, 0xef} 27 | 28 | key, err := bs.Put(data) 29 | if err != nil { 30 | t.Error(err) 31 | return 32 | } 33 | 34 | recv, err := bs.Get(key) 35 | if err != nil { 36 | t.Error(err) 37 | } 38 | 39 | if !bytes.Equal(recv, data) { 40 | t.Errorf("stored data and retrieved data does not match: %v vs. %v", recv, data) 41 | } 42 | } 43 | 44 | func TestReadNonExistantKey(t *testing.T) { 45 | dir, err := ioutil.TempDir("", "blobstore") 46 | if err != nil { 47 | t.Error(err) 48 | return 49 | } 50 | defer os.RemoveAll(dir) 51 | 52 | bs := Open(dir) 53 | 54 | h := sha1.New() 55 | h.Write([]byte{0x42}) 56 | key := hex.EncodeToString(h.Sum(nil)) 57 | buf, err := bs.Get(key) 58 | if err != ErrNoSuchKey { 59 | t.Errorf("Expected no such key %v, found it anyway. (buf=%v, err=%v)", key, buf, err) 60 | return 61 | } 62 | } 63 | 64 | func TestReadInvalidKeyLength(t *testing.T) { 65 | dir, err := ioutil.TempDir("", "blobstore") 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | defer os.RemoveAll(dir) 70 | 71 | bs := Open(dir) 72 | 73 | key := "" 74 | for i := 0; i < 5; i++ { 75 | key += "0" 76 | } 77 | 78 | _, err = bs.Get(key) 79 | if err != ErrBadKey { 80 | t.Errorf("Expected invalid key for %v, got %v", key, err) 81 | return 82 | } 83 | } 84 | 85 | func TestReadBadKeyNonHex(t *testing.T) { 86 | dir, err := ioutil.TempDir("", "blobstore") 87 | if err != nil { 88 | t.Error(err) 89 | return 90 | } 91 | defer os.RemoveAll(dir) 92 | 93 | bs := Open(dir) 94 | 95 | key := "" 96 | for i := 0; i < 40; i++ { 97 | key += "i" 98 | } 99 | 100 | _, err = bs.Get(key) 101 | if err != ErrBadKey { 102 | t.Errorf("Expected bad key for %v, got %v", key, err) 103 | return 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cmd/grumble/args.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "text/template" 9 | ) 10 | 11 | type UsageArgs struct { 12 | Version string 13 | BuildDate string 14 | OS string 15 | Arch string 16 | DefaultDataDir string 17 | } 18 | 19 | var usageTmpl = `usage: grumble [options] 20 | 21 | grumble {{.Version}} ({{.BuildDate}}) 22 | target: {{.OS}}, {{.Arch}} 23 | 24 | --help 25 | Shows this help listing. 26 | 27 | --datadir (default: {{.DefaultDataDir}}) 28 | Directory to use for server storage. 29 | 30 | --log (default: $DATADIR/grumble.log) 31 | Log file path. 32 | 33 | --regen-keys 34 | Force grumble to regenerate its global RSA 35 | keypair (and certificate). 36 | 37 | The global keypair lives in the root of the 38 | grumble data directory. 39 | 40 | --import-murmurdb 41 | Import a Murmur SQLite database into grumble. 42 | 43 | Use the --cleanup argument to force grumble to 44 | clean up its data directory when doing the 45 | import. This is *DESTRUCTIVE*! Use with care. 46 | ` 47 | 48 | type args struct { 49 | ShowHelp bool 50 | DataDir string 51 | LogPath string 52 | RegenKeys bool 53 | SQLiteDB string 54 | CleanUp bool 55 | } 56 | 57 | func defaultDataDir() string { 58 | homedir := os.Getenv("HOME") 59 | dirname := ".grumble" 60 | if runtime.GOOS == "windows" { 61 | homedir = os.Getenv("USERPROFILE") 62 | } 63 | return filepath.Join(homedir, dirname) 64 | } 65 | 66 | func defaultLogPath() string { 67 | return filepath.Join(defaultDataDir(), "grumble.log") 68 | } 69 | 70 | func Usage() { 71 | t, err := template.New("usage").Parse(usageTmpl) 72 | if err != nil { 73 | panic("unable to parse usage template") 74 | } 75 | 76 | err = t.Execute(os.Stdout, UsageArgs{ 77 | Version: version, 78 | BuildDate: buildDate, 79 | OS: runtime.GOOS, 80 | Arch: runtime.GOARCH, 81 | DefaultDataDir: defaultDataDir(), 82 | }) 83 | if err != nil { 84 | panic("unable to execute usage template") 85 | } 86 | } 87 | 88 | var Args args 89 | 90 | func init() { 91 | flag.Usage = Usage 92 | 93 | flag.BoolVar(&Args.ShowHelp, "help", false, "") 94 | flag.StringVar(&Args.DataDir, "datadir", defaultDataDir(), "") 95 | flag.StringVar(&Args.LogPath, "log", defaultLogPath(), "") 96 | flag.BoolVar(&Args.RegenKeys, "regen-keys", false, "") 97 | 98 | flag.StringVar(&Args.SQLiteDB, "import-murmurdb", "", "") 99 | flag.BoolVar(&Args.CleanUp, "cleanup", false, "") 100 | } 101 | -------------------------------------------------------------------------------- /pkg/mumbleproto/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package mumbleproto 6 | 7 | const ( 8 | MessageVersion uint16 = iota 9 | MessageUDPTunnel 10 | MessageAuthenticate 11 | MessagePing 12 | MessageReject 13 | MessageServerSync 14 | MessageChannelRemove 15 | MessageChannelState 16 | MessageUserRemove 17 | MessageUserState 18 | MessageBanList 19 | MessageTextMessage 20 | MessagePermissionDenied 21 | MessageACL 22 | MessageQueryUsers 23 | MessageCryptSetup 24 | MessageContextActionModify 25 | MessageContextAction 26 | MessageUserList 27 | MessageVoiceTarget 28 | MessagePermissionQuery 29 | MessageCodecVersion 30 | MessageUserStats 31 | MessageRequestBlob 32 | MessageServerConfig 33 | ) 34 | 35 | const ( 36 | UDPMessageVoiceCELTAlpha = iota 37 | UDPMessagePing 38 | UDPMessageVoiceSpeex 39 | UDPMessageVoiceCELTBeta 40 | UDPMessageVoiceOpus 41 | ) 42 | 43 | // MessageType returns the numeric value identifying the message type of msg on the wire. 44 | func MessageType(msg interface{}) uint16 { 45 | switch msg.(type) { 46 | case *Version: 47 | return MessageVersion 48 | case *UDPTunnel: 49 | case []byte: 50 | return MessageUDPTunnel 51 | case *Authenticate: 52 | return MessageAuthenticate 53 | case *Ping: 54 | return MessagePing 55 | case *Reject: 56 | return MessageReject 57 | case *ServerSync: 58 | return MessageServerSync 59 | case *ChannelRemove: 60 | return MessageChannelRemove 61 | case *ChannelState: 62 | return MessageChannelState 63 | case *UserRemove: 64 | return MessageUserRemove 65 | case *UserState: 66 | return MessageUserState 67 | case *BanList: 68 | return MessageBanList 69 | case *TextMessage: 70 | return MessageTextMessage 71 | case *PermissionDenied: 72 | return MessagePermissionDenied 73 | case *ACL: 74 | return MessageACL 75 | case *QueryUsers: 76 | return MessageQueryUsers 77 | case *CryptSetup: 78 | return MessageCryptSetup 79 | case *ContextActionModify: 80 | return MessageContextActionModify 81 | case *ContextAction: 82 | return MessageContextAction 83 | case *UserList: 84 | return MessageUserList 85 | case *VoiceTarget: 86 | return MessageVoiceTarget 87 | case *PermissionQuery: 88 | return MessagePermissionQuery 89 | case *CodecVersion: 90 | return MessageCodecVersion 91 | case *UserStats: 92 | return MessageUserStats 93 | case *RequestBlob: 94 | return MessageRequestBlob 95 | case *ServerConfig: 96 | return MessageServerConfig 97 | } 98 | panic("unknown type") 99 | } 100 | -------------------------------------------------------------------------------- /pkg/sessionpool/sessionpool.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // Package sessionpool implements a reuse pool for session IDs. 6 | package sessionpool 7 | 8 | import ( 9 | "math" 10 | "sync" 11 | ) 12 | 13 | // A SessionPool is a pool for session IDs. 14 | // IDs are re-used in MRU order, for ease of implementation in Go. 15 | type SessionPool struct { 16 | mutex sync.Mutex 17 | used map[uint32]bool 18 | unused []uint32 19 | cur uint32 20 | } 21 | 22 | // Create a new SessionPool container. 23 | func New() (pool *SessionPool) { 24 | pool = new(SessionPool) 25 | return 26 | } 27 | 28 | // Enable use-tracking for the SessionPool. 29 | // 30 | // When enabled, the SessionPool stores all session IDs 31 | // returned by Get() internally. When an ID is reclaimed, 32 | // the SessionPool checks whether the ID being reclaimed 33 | // is in its list of used IDs. If this is not the case, 34 | // the program will panic. 35 | func (pool *SessionPool) EnableUseTracking() { 36 | if len(pool.unused) != 0 || pool.cur != 0 { 37 | panic("Attempt to enable use tracking on an existing SessionPool.") 38 | } 39 | pool.used = make(map[uint32]bool) 40 | } 41 | 42 | // Get a new session ID from the SessionPool. 43 | // Must be reclaimed using Reclaim() when done using it. 44 | func (pool *SessionPool) Get() (id uint32) { 45 | pool.mutex.Lock() 46 | defer pool.mutex.Unlock() 47 | 48 | // If use tracking is enabled, mark our returned session id as used. 49 | if pool.used != nil { 50 | defer func() { 51 | pool.used[id] = true 52 | }() 53 | } 54 | 55 | // First, look in the unused stack. 56 | length := len(pool.unused) 57 | if length > 0 { 58 | id = pool.unused[length-1] 59 | pool.unused = pool.unused[:length-1] 60 | return 61 | } 62 | 63 | // Check for depletion. If cur is MaxUint32, 64 | // there aren't any session IDs left, since the 65 | // increment below would overflow us back to 0. 66 | if pool.cur == math.MaxUint32 { 67 | panic("SessionPool depleted") 68 | } 69 | 70 | // Increment the next session id and return it. 71 | // Note: By incrementing and *then* returning, we skip 0. 72 | // This is deliberate, as 0 is an invalid session ID in Mumble. 73 | pool.cur += 1 74 | id = pool.cur 75 | return 76 | } 77 | 78 | // Reclaim a session ID so it can be reused. 79 | func (pool *SessionPool) Reclaim(id uint32) { 80 | pool.mutex.Lock() 81 | defer pool.mutex.Unlock() 82 | 83 | // Check whether this ID is marked as being in use. 84 | if pool.used != nil { 85 | _, inUse := pool.used[id] 86 | if !inUse { 87 | panic("Attempt to reclaim invalid session ID") 88 | } 89 | delete(pool.used, id) 90 | } 91 | 92 | pool.unused = append(pool.unused, id) 93 | } 94 | -------------------------------------------------------------------------------- /pkg/blobstore/blobreader.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011-2013 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package blobstore 6 | 7 | import ( 8 | "bytes" 9 | "crypto/sha1" 10 | "encoding/hex" 11 | "hash" 12 | "io" 13 | ) 14 | 15 | // EOFHashMismatchError signals that a blobReader reached EOF, but that 16 | // the calculated hash did not match the given blob key. This signals 17 | // a successful read of the blob, but that the on-disk content is 18 | // corrupted in some fashion. 19 | type EOFHashMismatchError struct { 20 | // Sum represents that was calculated during the read operation. 21 | Sum []byte 22 | } 23 | 24 | func (hme EOFHashMismatchError) Error() string { 25 | return "blobstore: EOF hash mismatch" 26 | } 27 | 28 | // blobReader implements an io.ReadCloser that reads a blob from disk 29 | // and hashes all incoming data to ensure integrity. On EOF, it matches 30 | // its calculated hash with the given blob key in order to detect data 31 | // corruption. 32 | // 33 | // If a mismatch is detected on EOF, the blobReader will return 34 | // the error ErrEOFHashMismatch instead of a regular io.EOF error. 35 | type blobReader struct { 36 | rc io.ReadCloser 37 | sum []byte 38 | hash hash.Hash 39 | } 40 | 41 | // newBlobReader returns a new blobReader reading from rc. 42 | // The rc is expected to be a blobstore entry identified by 43 | // the given key. (The blobstore is content addressible, and 44 | // a blob's key represents the SHA1 of its content). 45 | func newBlobReader(rc io.ReadCloser, key string) (*blobReader, error) { 46 | sum, err := hex.DecodeString(key) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return &blobReader{rc, sum, sha1.New()}, nil 51 | } 52 | 53 | // Read implements the Read method of io.ReadCloser. 54 | // This Read implementation passes on read calls to the 55 | // wrapper io.ReadCloser and hashes all read content. 56 | // When EOF is reached, the sum of the streaming hash 57 | // hash is calculated and compared to the blob key given 58 | // in newBlobReader. If the calculated hash does not match 59 | // the blob key, the special error ErrEOFHashMismatch is 60 | // returned to signal EOF, while also signalling a hash 61 | // mismatch. 62 | func (r *blobReader) Read(b []byte) (int, error) { 63 | n, err := r.rc.Read(b) 64 | _, werr := r.hash.Write(b[:n]) 65 | if werr != nil { 66 | return 0, werr 67 | } 68 | if err != io.EOF { 69 | return n, err 70 | } 71 | // Match the calculated digest with the expected 72 | // digest on EOF. 73 | calcSum := r.hash.Sum(nil) 74 | if !bytes.Equal(r.sum, calcSum) { 75 | return 0, EOFHashMismatchError{Sum: calcSum} 76 | } 77 | return n, io.EOF 78 | } 79 | 80 | // Close implements the Close method of io.ReadCloser. 81 | // This Close method simply closes the wrapped io.ReadCloser. 82 | func (r *blobReader) Close() error { 83 | return r.rc.Close() 84 | } 85 | -------------------------------------------------------------------------------- /pkg/logtarget/logtarget.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // Package logtarget implements a multiplexing logging target 6 | package logtarget 7 | 8 | import ( 9 | "io" 10 | "os" 11 | "sync" 12 | ) 13 | 14 | // LogTarget implements the io.Writer interface, allowing 15 | // LogTarget to be registered with the regular Go log package. 16 | // LogTarget multiplexes its incoming writes to multiple optional 17 | // output writers, and one main output writer (the log file). 18 | type LogTarget interface { 19 | io.Writer 20 | 21 | Rotate() error 22 | } 23 | 24 | // logTarget is the default implementation of a log 25 | // target. It can write to more than one writer at the same time 26 | // but only rotate one file 27 | type logTarget struct { 28 | mu sync.Mutex 29 | logfn string 30 | file *os.File 31 | w io.Writer 32 | ws []io.Writer 33 | } 34 | 35 | // Default is the default log target for the application 36 | // It has to be initialized before used 37 | var Default LogTarget 38 | 39 | // OpenWriters returns a log target that will 40 | // log to all the given writers at the same time 41 | func OpenWriters(ws ...io.Writer) LogTarget { 42 | target := &logTarget{} 43 | target.w = io.MultiWriter(ws...) 44 | return target 45 | } 46 | 47 | // OpenFile creates a LogTarget pointing to a log file 48 | // and returns it. 49 | // This method will open the file in append-only mode. 50 | // It also takes a variable number of writers that are 51 | // other log targets 52 | func OpenFile(fileName string, ws ...io.Writer) (t LogTarget, err error) { 53 | target := &logTarget{} 54 | target.logfn = fileName 55 | target.file, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0650) 56 | if err != nil { 57 | return nil, err 58 | } 59 | target.ws = ws 60 | target.w = io.MultiWriter(append(ws, target.file)...) 61 | return target, nil 62 | } 63 | 64 | // Write writes a log message to all registered io.Writers 65 | func (target *logTarget) Write(out []byte) (int, error) { 66 | target.mu.Lock() 67 | defer target.mu.Unlock() 68 | 69 | return target.w.Write(out) 70 | } 71 | 72 | // Rotate rotates the current log file, if one is opened. 73 | // This method holds a lock while rotating the log file, 74 | // and all log writes will be held back until the rotation 75 | // is complete. 76 | func (target *logTarget) Rotate() error { 77 | target.mu.Lock() 78 | defer target.mu.Unlock() 79 | 80 | if target.file == nil { 81 | return nil 82 | } 83 | 84 | // Close the existing log file 85 | err := target.file.Close() 86 | if err != nil { 87 | return err 88 | } 89 | 90 | target.file, err = os.OpenFile(target.logfn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0650) 91 | if err != nil { 92 | return err 93 | } 94 | target.w = io.MultiWriter(append(target.ws, target.file)...) 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /pkg/serverconf/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package serverconf 6 | 7 | import ( 8 | "strconv" 9 | "sync" 10 | ) 11 | 12 | var defaultCfg = map[string]string{ 13 | "MaxBandwidth": "72000", 14 | "MaxUsers": "1000", 15 | "MaxUsersPerChannel": "0", 16 | "MaxTextMessageLength": "5000", 17 | "MaxImageMessageLength": "131072", 18 | "AllowHTML": "true", 19 | "DefaultChannel": "0", 20 | "RememberChannel": "true", 21 | "WelcomeText": "Welcome to this server running Grumble.", 22 | "SendVersion": "true", 23 | } 24 | 25 | type Config struct { 26 | cfgMap map[string]string 27 | mutex sync.RWMutex 28 | } 29 | 30 | // Create a new Config using cfgMap as the intial internal config map. 31 | // If cfgMap is nil, ConfigWithMap will create a new config map. 32 | func New(cfgMap map[string]string) *Config { 33 | if cfgMap == nil { 34 | cfgMap = make(map[string]string) 35 | } 36 | return &Config{cfgMap: cfgMap} 37 | } 38 | 39 | // GetAll gets a copy of the Config's internal config map 40 | func (cfg *Config) GetAll() (all map[string]string) { 41 | cfg.mutex.RLock() 42 | defer cfg.mutex.RUnlock() 43 | 44 | all = make(map[string]string) 45 | for k, v := range cfg.cfgMap { 46 | all[k] = v 47 | } 48 | return 49 | } 50 | 51 | // Set a new value for a config key 52 | func (cfg *Config) Set(key string, value string) { 53 | cfg.mutex.Lock() 54 | defer cfg.mutex.Unlock() 55 | cfg.cfgMap[key] = value 56 | } 57 | 58 | // Reset the value of a config key 59 | func (cfg *Config) Reset(key string) { 60 | cfg.mutex.Lock() 61 | defer cfg.mutex.Unlock() 62 | delete(cfg.cfgMap, key) 63 | } 64 | 65 | // StringValue gets the value of a specific config key encoded as a string 66 | func (cfg *Config) StringValue(key string) (value string) { 67 | cfg.mutex.RLock() 68 | defer cfg.mutex.RUnlock() 69 | 70 | value, exists := cfg.cfgMap[key] 71 | if exists { 72 | return value 73 | } 74 | 75 | value, exists = defaultCfg[key] 76 | if exists { 77 | return value 78 | } 79 | 80 | return "" 81 | } 82 | 83 | // IntValue gets the value of a speific config key as an int 84 | func (cfg *Config) IntValue(key string) (intval int) { 85 | str := cfg.StringValue(key) 86 | intval, _ = strconv.Atoi(str) 87 | return 88 | } 89 | 90 | // Uint32Value gets the value of a specific config key as a uint32 91 | func (cfg *Config) Uint32Value(key string) (uint32val uint32) { 92 | str := cfg.StringValue(key) 93 | uintval, _ := strconv.ParseUint(str, 10, 0) 94 | return uint32(uintval) 95 | } 96 | 97 | // BoolValue gets the value fo a sepcific config key as a bool 98 | func (cfg *Config) BoolValue(key string) (boolval bool) { 99 | str := cfg.StringValue(key) 100 | boolval, _ = strconv.ParseBool(str) 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /pkg/cryptstate/testgen/CryptState.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2005-2010, Thorvald Natvig 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | - Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | - Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | - Neither the name of the Mumble Developers nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 22 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef _CRYPTSTATE_H 32 | #define _CRYPTSTATE_H 33 | 34 | #include 35 | 36 | namespace MumbleClient { 37 | 38 | class CryptState { 39 | public: 40 | unsigned char raw_key[AES_BLOCK_SIZE]; 41 | unsigned char encrypt_iv[AES_BLOCK_SIZE]; 42 | unsigned char decrypt_iv[AES_BLOCK_SIZE]; 43 | unsigned char decrypt_history[0x100]; 44 | 45 | unsigned int uiGood; 46 | unsigned int uiLate; 47 | unsigned int uiLost; 48 | unsigned int uiResync; 49 | 50 | unsigned int uiRemoteGood; 51 | unsigned int uiRemoteLate; 52 | unsigned int uiRemoteLost; 53 | unsigned int uiRemoteResync; 54 | 55 | AES_KEY encrypt_key; 56 | AES_KEY decrypt_key; 57 | bool bInit; 58 | 59 | public: 60 | CryptState(); 61 | 62 | bool isValid() const; 63 | void genKey(); 64 | void setKey(const unsigned char* rkey, const unsigned char* eiv, const unsigned char* div); 65 | void setDecryptIV(const unsigned char* iv); 66 | const unsigned char* getEncryptIV() const; 67 | 68 | void ocb_encrypt(const unsigned char* plain, unsigned char* encrypted, unsigned int len, const unsigned char* nonce, unsigned char* tag); 69 | void ocb_decrypt(const unsigned char* encrypted, unsigned char* plain, unsigned int len, const unsigned char* nonce, unsigned char* tag); 70 | 71 | bool decrypt(const unsigned char* source, unsigned char* dst, unsigned int crypted_length); 72 | void encrypt(const unsigned char* source, unsigned char* dst, unsigned int plain_length); 73 | }; 74 | 75 | } // end namespace MumbleClient 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /pkg/packetdata/packetdata_test.go: -------------------------------------------------------------------------------- 1 | package packetdata 2 | 3 | import ( 4 | "crypto/rand" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestSelfUint8(t *testing.T) { 10 | buf := make([]byte, 500) 11 | pds := New(buf) 12 | 13 | for i := uint8(0); i < 0xff; i++ { 14 | pds.PutUint8(i) 15 | if !pds.IsValid() { 16 | t.Errorf("Invalid PDS") 17 | return 18 | } 19 | } 20 | 21 | pds2 := New(pds.Buf) 22 | for i := uint8(0); i < 0xff; i++ { 23 | val := pds2.GetUint8() 24 | if val != i { 25 | t.Errorf("Mismatch (read: %v, expected: %v)", val, i) 26 | return 27 | } 28 | } 29 | } 30 | 31 | func TestSelfUint64(t *testing.T) { 32 | buf := make([]byte, 500) 33 | pds := New(buf) 34 | 35 | for i := uint64(1 << 54); i < (uint64(1<<54) + 10); i++ { 36 | pds.PutUint64(i) 37 | if !pds.IsValid() { 38 | t.Errorf("Invalid PDS") 39 | return 40 | } 41 | } 42 | 43 | pds2 := New(buf) 44 | for i := uint64(1 << 54); i < (uint64(1<<54) + 10); i++ { 45 | val := pds2.GetUint64() 46 | if !pds.IsValid() { 47 | t.Errorf("Invalid PDS") 48 | } 49 | if val != i { 50 | t.Errorf("Mismatch (read: %v, expected: %v)", val, i) 51 | return 52 | } 53 | } 54 | } 55 | 56 | func TestSelfMumbleVoicePacket(t *testing.T) { 57 | buf := make([]byte, 500) 58 | pds := New(buf) 59 | data := make([]byte, 54) 60 | 61 | rand.Read(data) 62 | 63 | pds.PutUint32(1) 64 | pds.PutBytes(data) 65 | 66 | pds2 := New(buf) 67 | if pds2.GetUint32() != 1 { 68 | t.Errorf("Session mismatch") 69 | } 70 | 71 | outbuf := make([]byte, 54) 72 | pds2.CopyBytes(outbuf) 73 | 74 | if !pds.IsValid() { 75 | t.Errorf("Invalid PDS") 76 | return 77 | } 78 | 79 | for i := 0; i < 54; i++ { 80 | if outbuf[i] != data[i] { 81 | t.Errorf("Voice data mismatch (got %v, expected %v)", outbuf[i], data[i]) 82 | return 83 | } 84 | } 85 | } 86 | 87 | func TestSelfFloat64(t *testing.T) { 88 | buf := make([]byte, 500) 89 | pds := New(buf) 90 | pds2 := New(buf) 91 | 92 | pds.PutFloat64(math.Pi) 93 | pi := pds2.GetFloat64() 94 | 95 | if !pds.IsValid() || !pds2.IsValid() { 96 | t.Errorf("Invalid PDS") 97 | return 98 | } 99 | 100 | if pi != float64(math.Pi) { 101 | t.Errorf("Unexpected result. Got %v, expected %v", pi, float64(math.Pi)) 102 | return 103 | } 104 | } 105 | 106 | func TestSelfFloat32(t *testing.T) { 107 | buf := make([]byte, 500) 108 | pds := New(buf) 109 | pds2 := New(buf) 110 | 111 | pds.PutFloat32(math.E) 112 | e := pds2.GetFloat32() 113 | 114 | if !pds.IsValid() || !pds2.IsValid() { 115 | t.Errorf("Invalid PDS") 116 | } 117 | 118 | if e != float32(math.E) { 119 | t.Errorf("Unexpected result. Got %v, expected %v", e, float32(math.E)) 120 | } 121 | } 122 | 123 | func TestSelfBytes(t *testing.T) { 124 | msg := [15]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf} 125 | buf := make([]byte, 500) 126 | pds := New(buf) 127 | pds2 := New(buf) 128 | 129 | pds.PutBytes(msg[0:]) 130 | out := make([]byte, 15) 131 | pds2.CopyBytes(out) 132 | 133 | if !pds.IsValid() || !pds.IsValid() { 134 | t.Errorf("Invalid PDS") 135 | return 136 | } 137 | 138 | for i := 0; i < 15; i++ { 139 | if msg[i] != out[i] { 140 | t.Errorf("Mismatch at index %v. Got %v, expected %v", i, out[i], msg[i]) 141 | return 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /pkg/ban/ban_test.go: -------------------------------------------------------------------------------- 1 | package ban 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestMaskNonPowerOf8(t *testing.T) { 11 | mask := []byte{0xff, 0x1f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 12 | b := Ban{} 13 | b.Mask = 13 14 | if !bytes.Equal(b.IPMask(), mask) { 15 | t.Errorf("Mask mismatch: %v, %v", mask, []byte(b.IPMask())) 16 | } 17 | } 18 | 19 | func TestMaksPowerOf2(t *testing.T) { 20 | mask := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0} 21 | b := Ban{} 22 | b.Mask = 64 23 | if !bytes.Equal(b.IPMask(), mask) { 24 | t.Errorf("Mask mismatch: %v, %v", mask, []byte(b.IPMask())) 25 | } 26 | } 27 | 28 | func TestMatchV4(t *testing.T) { 29 | b := Ban{} 30 | b.IP = net.ParseIP("192.168.1.1") 31 | b.Mask = 24 + 96 // ipv4 /24 32 | if len(b.IP) == 0 { 33 | t.Errorf("Invalid IP") 34 | } 35 | 36 | clientIp := net.ParseIP("192.168.1.50") 37 | if len(clientIp) == 0 { 38 | t.Errorf("Invalid IP") 39 | } 40 | 41 | if b.Match(clientIp) != true { 42 | t.Errorf("IPv4: unexpected match") 43 | } 44 | } 45 | 46 | func TestMismatchV4(t *testing.T) { 47 | b := Ban{} 48 | b.IP = net.ParseIP("192.168.1.1") 49 | b.Mask = 24 + 96 // ipv4 /24 50 | if len(b.IP) == 0 { 51 | t.Errorf("Invalid IP") 52 | } 53 | 54 | clientIp := net.ParseIP("192.168.2.1") 55 | if len(clientIp) == 0 { 56 | t.Errorf("Invalid IP") 57 | } 58 | 59 | if b.Match(clientIp) == true { 60 | t.Errorf("IPv4: unexpected mismatch") 61 | } 62 | } 63 | 64 | func TestMatchV6(t *testing.T) { 65 | b := Ban{} 66 | b.IP = net.ParseIP("2a00:1450:400b:c00::63") 67 | b.Mask = 64 68 | if len(b.IP) == 0 { 69 | t.Errorf("Invalid IP") 70 | } 71 | 72 | clientIp := net.ParseIP("2a00:1450:400b:c00::54") 73 | if len(clientIp) == 0 { 74 | t.Errorf("Invalid IP") 75 | } 76 | 77 | if b.Match(clientIp) != true { 78 | t.Errorf("IPv6: unexpected match") 79 | } 80 | } 81 | 82 | func TestMismatchV6(t *testing.T) { 83 | b := Ban{} 84 | b.IP = net.ParseIP("2a00:1450:400b:c00::63") 85 | b.Mask = 64 86 | 87 | if len(b.IP) == 0 { 88 | t.Errorf("Invalid IP") 89 | } 90 | 91 | clientIp := net.ParseIP("2a00:1450:400b:deaf:42f0:cafe:babe:54") 92 | if len(clientIp) == 0 { 93 | t.Errorf("Invalid IP") 94 | } 95 | 96 | if b.Match(clientIp) == true { 97 | t.Errorf("IPv6: unexpected mismatch") 98 | } 99 | } 100 | 101 | func TestISODate(t *testing.T) { 102 | sometime := "2011-05-14T13:48:00" 103 | b := Ban{} 104 | b.SetISOStartDate(sometime) 105 | if sometime != b.ISOStartDate() { 106 | t.Errorf("UNIX timestamp mismatch: %v %v", b.ISOStartDate(), sometime) 107 | } 108 | } 109 | 110 | func TestInfiniteExpiry(t *testing.T) { 111 | b := Ban{} 112 | b.Start = time.Now().Add(-10 * time.Second).Unix() 113 | b.Duration = 0 114 | 115 | if b.IsExpired() { 116 | t.Errorf("∞ should not expire") 117 | } 118 | } 119 | 120 | func TestExpired(t *testing.T) { 121 | b := Ban{} 122 | b.Start = time.Now().Add(-10 * time.Second).Unix() 123 | b.Duration = 9 124 | 125 | if !b.IsExpired() { 126 | t.Errorf("Should have expired 1 second ago") 127 | } 128 | } 129 | 130 | func TestNotExpired(t *testing.T) { 131 | b := Ban{} 132 | b.Start = time.Now().Unix() 133 | b.Duration = 60 * 60 * 24 134 | 135 | if b.IsExpired() { 136 | t.Errorf("Should expire in 24 hours") 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Linux CI (Travis CI): 3 | 4 | [![Build Status](https://travis-ci.com/mumble-voip/grumble.svg?branch=master)](https://travis-ci.com/mumble-voip/grumble) 5 | 6 | Windows CI (AppVeyor): 7 | 8 | [![Build status](https://ci.appveyor.com/api/projects/status/yfvg0eagpuy9kgg9/branch/master?svg=true)](https://ci.appveyor.com/project/mumble-voip/grumble/branch/master) 9 | 10 | Go: 11 | 12 | [![Go Report Card](https://goreportcard.com/badge/github.com/mumble-voip/grumble)](https://goreportcard.com/report/github.com/mumble-voip/grumble) 13 | 14 | 15 | What is Grumble? 16 | ================ 17 | 18 | Grumble is an implementation of a server for the Mumble voice chat system. It is an alternative to Murmur, the typical Mumble server. 19 | 20 | Compiling Grumble from source 21 | ============================= 22 | 23 | You must have a Go 1 environment installed to build Grumble. Those are available at: 24 | 25 | https://golang.org/dl/ 26 | 27 | Once Go is installed, you should set up a GOPATH to avoid clobbering your Go environment's root directory with third party packages. 28 | 29 | Set up a GOPATH. On Unix, do something like this 30 | ```shell script 31 | $ export GOPATH=$HOME/gocode 32 | $ mkdir -p $GOPATH 33 | ``` 34 | 35 | and on Windows, do something like this (for cmd.exe): 36 | ```shell script 37 | c:\> set GOPATH=%USERPROFILE%\gocode 38 | c:\> mkdir %GOPATH% 39 | ``` 40 | 41 | Then, it's time to install Grumble. The following line should do the trick: 42 | ```shell script 43 | $ go get mumble.info/grumble/cmd/grumble 44 | ``` 45 | 46 | And that should be it. Grumble has been built, and is available in $GOPATH/bin as 'grumble'. 47 | 48 | Project status 49 | ============== 50 | 51 | Grumble is pretty much feature complete, except for a few "minor" things. 52 | 53 | There is no bandwidth limiting, and there is no API to remote control it. 54 | 55 | Grumble's persistence layer is very ad-hoc. It uses an append-only file to store delta updates to each server's internal data, and periodically, it syncs a server's full data to disk. 56 | 57 | Grumble is currently architected to have all data in memory. That means it's not ideal for use with very very large servers. (And large servers in this context are servers with many registered users, ACLs, etc.). 58 | 59 | It is architected this way because it allowed me to write a pure-Go program with very few external dependencies, back 4-5 years ago. 60 | 61 | The current thinking is that if registered users are taking up too much of your memory, you should use an external authenticator. But that code isn't written yet. The concept would be equivalent to Murmur's authenticator API via RPC. But a Grumble authenticator would probably be set up more akin to a webhook -- so just a URL in the config file. 62 | 63 | Then there's the API problem. You can't currently remote control Grumble. Which can make it hard to use in production. I imagine Grumble will grow an API that it makes available via HTTP. Murmur's API is already quite stateless in many regards, so it shouldn't be too much of a stretch to put a RESTful API in Grumble to do the same job. 64 | 65 | Docker 66 | ============== 67 | 68 | ## Getting the image 69 | 70 | ### Building 71 | ```shell script 72 | $ git clone https://github.com/mumble-voip/grumble.git 73 | $ cd grumble/ 74 | $ docker build -t mumble-voip/grumble . 75 | ``` 76 | 77 | ## Running 78 | 79 | ### Command line 80 | ```shell script 81 | $ docker run \ 82 | -v $HOME/.grumble:/data \ 83 | -p 64738:64738 \ 84 | -p 64738:64738/udp \ 85 | mumble-voip/grumble 86 | ``` 87 | 88 | ### Compose 89 | ```yaml 90 | version: '3' 91 | services: 92 | grumble: 93 | image: mumble-voip/grumble 94 | ports: 95 | - 64738:64738 96 | - 64738:64738/udp 97 | volumes: 98 | - $HOME/.grumble:/data 99 | ``` 100 | -------------------------------------------------------------------------------- /cmd/grumble/channel.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/hex" 9 | 10 | "mumble.info/grumble/pkg/acl" 11 | ) 12 | 13 | // A Mumble channel 14 | type Channel struct { 15 | Id int 16 | Name string 17 | Position int 18 | 19 | temporary bool 20 | clients map[uint32]*Client 21 | parent *Channel 22 | children map[int]*Channel 23 | 24 | // ACL 25 | ACL acl.Context 26 | 27 | // Links 28 | Links map[int]*Channel 29 | 30 | // Blobs 31 | DescriptionBlob string 32 | } 33 | 34 | func NewChannel(id int, name string) (channel *Channel) { 35 | channel = new(Channel) 36 | channel.Id = id 37 | channel.Name = name 38 | channel.clients = make(map[uint32]*Client) 39 | channel.children = make(map[int]*Channel) 40 | channel.ACL.Groups = make(map[string]acl.Group) 41 | channel.Links = make(map[int]*Channel) 42 | return 43 | } 44 | 45 | // AddChild adds a child channel to a channel 46 | func (channel *Channel) AddChild(child *Channel) { 47 | child.parent = channel 48 | child.ACL.Parent = &channel.ACL 49 | channel.children[child.Id] = child 50 | } 51 | 52 | // RemoveChild removes a child channel from a parent 53 | func (channel *Channel) RemoveChild(child *Channel) { 54 | child.parent = nil 55 | child.ACL.Parent = nil 56 | delete(channel.children, child.Id) 57 | } 58 | 59 | // AddClient adds client 60 | func (channel *Channel) AddClient(client *Client) { 61 | channel.clients[client.Session()] = client 62 | client.Channel = channel 63 | } 64 | 65 | // RemoveClient removes client 66 | func (channel *Channel) RemoveClient(client *Client) { 67 | delete(channel.clients, client.Session()) 68 | client.Channel = nil 69 | } 70 | 71 | // HasDescription Does the channel have a description? 72 | func (channel *Channel) HasDescription() bool { 73 | return len(channel.DescriptionBlob) > 0 74 | } 75 | 76 | // DescriptionBlobHashBytes gets the channel's blob hash as a byte slice for sending via a protobuf message. 77 | // Returns nil if there is no blob. 78 | func (channel *Channel) DescriptionBlobHashBytes() (buf []byte) { 79 | buf, err := hex.DecodeString(channel.DescriptionBlob) 80 | if err != nil { 81 | return nil 82 | } 83 | return buf 84 | } 85 | 86 | // AllLinks returns a slice of all channels in this channel's 87 | // link chain. 88 | func (channel *Channel) AllLinks() (seen map[int]*Channel) { 89 | seen = make(map[int]*Channel) 90 | walk := []*Channel{channel} 91 | for len(walk) > 0 { 92 | current := walk[len(walk)-1] 93 | walk = walk[0 : len(walk)-1] 94 | for _, linked := range current.Links { 95 | if _, alreadySeen := seen[linked.Id]; !alreadySeen { 96 | seen[linked.Id] = linked 97 | walk = append(walk, linked) 98 | } 99 | } 100 | } 101 | return 102 | } 103 | 104 | // AllSubChannels returns a slice of all of this channel's subchannels. 105 | func (channel *Channel) AllSubChannels() (seen map[int]*Channel) { 106 | seen = make(map[int]*Channel) 107 | walk := []*Channel{} 108 | if len(channel.children) > 0 { 109 | walk = append(walk, channel) 110 | for len(walk) > 0 { 111 | current := walk[len(walk)-1] 112 | walk = walk[0 : len(walk)-1] 113 | for _, child := range current.children { 114 | if _, alreadySeen := seen[child.Id]; !alreadySeen { 115 | seen[child.Id] = child 116 | walk = append(walk, child) 117 | } 118 | } 119 | } 120 | } 121 | return 122 | } 123 | 124 | // IsTemporary checks whether the channel is temporary 125 | func (channel *Channel) IsTemporary() bool { 126 | return channel.temporary 127 | } 128 | 129 | // IsEmpty checks whether the channel is temporary 130 | func (channel *Channel) IsEmpty() bool { 131 | return len(channel.clients) == 0 132 | } 133 | -------------------------------------------------------------------------------- /cmd/grumble/register.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | // This file handles public server list registration 8 | 9 | import ( 10 | "bytes" 11 | "crypto/sha1" 12 | "crypto/tls" 13 | "encoding/hex" 14 | "encoding/xml" 15 | "io/ioutil" 16 | "net/http" 17 | ) 18 | 19 | type Register struct { 20 | XMLName xml.Name `xml:"server"` 21 | Version string `xml:"version"` 22 | Release string `xml:"release"` 23 | Name string `xml:"name"` 24 | Host string `xml:"host"` 25 | Password string `xml:"password"` 26 | Port int `xml:"port"` 27 | Url string `xml:"url"` 28 | Digest string `xml:"digest"` 29 | Users int `xml:"users"` 30 | Channels int `xml:"channels"` 31 | Location string `xml:"location"` 32 | } 33 | 34 | const registerUrl = "https://mumble.info/register.cgi" 35 | 36 | // Determines whether a server is public by checking whether the 37 | // config values required for public registration are set. 38 | // 39 | // This function is used to determine whether or not to periodically 40 | // contact the master server list and update this server's metadata. 41 | func (server *Server) IsPublic() bool { 42 | if len(server.cfg.StringValue("RegisterName")) == 0 { 43 | return false 44 | } 45 | if len(server.cfg.StringValue("RegisterHost")) == 0 { 46 | return false 47 | } 48 | if len(server.cfg.StringValue("RegisterPassword")) == 0 { 49 | return false 50 | } 51 | if len(server.cfg.StringValue("RegisterWebUrl")) == 0 { 52 | return false 53 | } 54 | return true 55 | } 56 | 57 | // Perform a public server registration update. 58 | // 59 | // When a Mumble server connects to the master server 60 | // for registration, it connects using its server certificate 61 | // as a client certificate for authentication purposes. 62 | func (server *Server) RegisterPublicServer() { 63 | if !server.IsPublic() { 64 | return 65 | } 66 | 67 | // Fetch the server's certificates and put them in a tls.Config. 68 | // We need the certificate chain to be able to use it in our client 69 | // certificate chain to the registration server, and we also need to 70 | // include a digest of the leaf certiifcate in the registration XML document 71 | // we send off to the server. 72 | config := &tls.Config{} 73 | for _, cert := range server.tlscfg.Certificates { 74 | config.Certificates = append(config.Certificates, cert) 75 | } 76 | 77 | hasher := sha1.New() 78 | hasher.Write(config.Certificates[0].Certificate[0]) 79 | digest := hex.EncodeToString(hasher.Sum(nil)) 80 | 81 | // Render registration XML template 82 | reg := Register{ 83 | Name: server.cfg.StringValue("RegisterName"), 84 | Host: server.cfg.StringValue("RegisterHost"), 85 | Password: server.cfg.StringValue("RegisterPassword"), 86 | Url: server.cfg.StringValue("RegisterWebUrl"), 87 | Location: server.cfg.StringValue("RegisterLocation"), 88 | Port: server.CurrentPort(), 89 | Digest: digest, 90 | Users: len(server.clients), 91 | Channels: len(server.Channels), 92 | Version: "1.2.4", 93 | Release: "Grumble Git", 94 | } 95 | buf := bytes.NewBuffer(nil) 96 | err := xml.NewEncoder(buf).Encode(reg) 97 | if err != nil { 98 | server.Printf("register: unable to marshal xml: %v", err) 99 | return 100 | } 101 | 102 | // Post registration XML data to server asynchronously in its own goroutine 103 | go func() { 104 | tr := &http.Transport{ 105 | TLSClientConfig: config, 106 | } 107 | client := &http.Client{Transport: tr} 108 | r, err := client.Post(registerUrl, "text/xml", ioutil.NopCloser(buf)) 109 | if err != nil { 110 | server.Printf("register: unable to post registration request: %v", err) 111 | return 112 | } 113 | bodyBytes, err := ioutil.ReadAll(r.Body) 114 | if err == nil { 115 | registerMsg := string(bodyBytes) 116 | if r.StatusCode == 200 { 117 | server.Printf("register: %v", registerMsg) 118 | } else { 119 | server.Printf("register: (status %v) %v", r.StatusCode, registerMsg) 120 | } 121 | } else { 122 | server.Printf("register: unable to read post response: %v", err) 123 | return 124 | } 125 | }() 126 | } 127 | -------------------------------------------------------------------------------- /pkg/htmlfilter/htmlfilter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package htmlfilter 6 | 7 | import ( 8 | "bytes" 9 | "encoding/xml" 10 | "errors" 11 | "io" 12 | "strings" 13 | ) 14 | 15 | type Options struct { 16 | StripHTML bool 17 | MaxTextMessageLength int 18 | MaxImageMessageLength int 19 | } 20 | 21 | var defaultOptions Options = Options{ 22 | StripHTML: true, 23 | MaxTextMessageLength: 1024, 24 | MaxImageMessageLength: 1024 * 1024, 25 | } 26 | 27 | var ( 28 | ErrExceedsTextMessageLength = errors.New("Exceeds text message length") 29 | ErrExceedsImageMessageLength = errors.New("Exceeds image message length") 30 | ) 31 | 32 | // Filter text according to options. 33 | func Filter(text string, options *Options) (filtered string, err error) { 34 | // This function filters incoming text from clients according to the three options: 35 | // 36 | // StripHTML: 37 | // If true, all HTML shall be stripped. 38 | // When stripping br tags, append a newline to the output stream. 39 | // When stripping p tags, append a newline after the end tag. 40 | // 41 | // MaxTextsageLength: 42 | // Text length for "plain" messages (messages without images) 43 | // 44 | // MaxImageMessageLength: 45 | // Text length for messages with images. 46 | 47 | if options == nil { 48 | options = &defaultOptions 49 | } 50 | 51 | max := options.MaxTextMessageLength 52 | maximg := options.MaxImageMessageLength 53 | 54 | if options.StripHTML { 55 | // Does the message include HTML? If not, take the fast path. 56 | if strings.Index(text, "<") == -1 { 57 | filtered = strings.TrimSpace(text) 58 | } else { 59 | // Strip away all HTML 60 | out := bytes.NewBuffer(nil) 61 | buf := bytes.NewBufferString(text) 62 | parser := xml.NewDecoder(buf) 63 | parser.Strict = false 64 | parser.AutoClose = xml.HTMLAutoClose 65 | parser.Entity = xml.HTMLEntity 66 | for { 67 | tok, err := parser.Token() 68 | if err == io.EOF { 69 | break 70 | } else if err != nil { 71 | return "", err 72 | } 73 | 74 | switch t := tok.(type) { 75 | case xml.CharData: 76 | out.Write(t) 77 | case xml.EndElement: 78 | if t.Name.Local == "p" || t.Name.Local == "br" { 79 | out.WriteString("\n") 80 | } 81 | } 82 | } 83 | filtered = strings.TrimSpace(out.String()) 84 | } 85 | if max != 0 && len(filtered) > max { 86 | return "", ErrExceedsTextMessageLength 87 | } 88 | } else { 89 | // No limits 90 | if max == 0 && maximg == 0 { 91 | return text, nil 92 | } 93 | 94 | // Too big for images? 95 | if maximg != 0 && len(text) > maximg { 96 | return "", ErrExceedsImageMessageLength 97 | } 98 | 99 | // Under max plain length? 100 | if max == 0 || len(text) <= max { 101 | return text, nil 102 | } 103 | 104 | // Over max length, under image limit. If text doesn't include 105 | // any HTML, this is a no-go. If there is HTML, we can attempt to 106 | // strip away data URIs to see if we can get the message to fit 107 | // into the plain message limit. 108 | if strings.Index(text, "<") == -1 { 109 | return "", ErrExceedsTextMessageLength 110 | } 111 | 112 | // Simplify the received HTML data by stripping away data URIs 113 | out := bytes.NewBuffer(nil) 114 | buf := bytes.NewBufferString(text) 115 | parser := xml.NewDecoder(buf) 116 | parser.Strict = false 117 | parser.AutoClose = xml.HTMLAutoClose 118 | parser.Entity = xml.HTMLEntity 119 | for { 120 | tok, err := parser.Token() 121 | if err == io.EOF { 122 | break 123 | } else if err != nil { 124 | return "", err 125 | } 126 | 127 | switch t := tok.(type) { 128 | case xml.CharData: 129 | out.Write(t) 130 | case xml.StartElement: 131 | out.WriteString("<") 132 | xml.Escape(out, []byte(t.Name.Local)) 133 | for _, attr := range t.Attr { 134 | if t.Name.Local == "img" && attr.Name.Local == "src" { 135 | continue 136 | } 137 | out.WriteString(" ") 138 | xml.Escape(out, []byte(attr.Name.Local)) 139 | out.WriteString(`="`) 140 | out.WriteString(attr.Value) 141 | out.WriteString(`"`) 142 | } 143 | out.WriteString(">") 144 | case xml.EndElement: 145 | out.WriteString("") 148 | } 149 | } 150 | 151 | filtered = strings.TrimSpace(out.String()) 152 | if len(filtered) > max { 153 | return "", ErrExceedsTextMessageLength 154 | } 155 | } 156 | 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /cmd/grumble/voicetarget.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | import "mumble.info/grumble/pkg/acl" 8 | 9 | // A VoiceTarget holds information about a single 10 | // VoiceTarget entry of a Client. 11 | type VoiceTarget struct { 12 | sessions []uint32 13 | channels []voiceTargetChannel 14 | 15 | directCache map[uint32]*Client 16 | fromChannelsCache map[uint32]*Client 17 | } 18 | 19 | type voiceTargetChannel struct { 20 | id uint32 21 | subChannels bool 22 | links bool 23 | onlyGroup string 24 | } 25 | 26 | // Add's a client's session to the VoiceTarget 27 | func (vt *VoiceTarget) AddSession(session uint32) { 28 | vt.sessions = append(vt.sessions, session) 29 | } 30 | 31 | // AddChannel adds a channel to the VoiceTarget. 32 | // If subchannels is true, any sent voice packets will also be sent to all subchannels. 33 | // If links is true, any sent voice packets will also be sent to all linked channels. 34 | // If group is a non-empty string, any sent voice packets will only be broadcast to members 35 | // of that group who reside in the channel (or its children or linked channels). 36 | func (vt *VoiceTarget) AddChannel(id uint32, subchannels bool, links bool, group string) { 37 | vt.channels = append(vt.channels, voiceTargetChannel{ 38 | id: id, 39 | subChannels: subchannels, 40 | links: links, 41 | onlyGroup: group, 42 | }) 43 | } 44 | 45 | // IsEmpty checks whether the VoiceTarget is empty (has no targets) 46 | func (vt *VoiceTarget) IsEmpty() bool { 47 | return len(vt.sessions) == 0 && len(vt.channels) == 0 48 | } 49 | 50 | // ClearCache clears the VoiceTarget's cache. 51 | func (vt *VoiceTarget) ClearCache() { 52 | vt.directCache = nil 53 | vt.fromChannelsCache = nil 54 | } 55 | 56 | // Send the contents of the VoiceBroadcast to all targets specified in the 57 | // VoiceTarget. 58 | func (vt *VoiceTarget) SendVoiceBroadcast(vb *VoiceBroadcast) { 59 | buf := vb.buf 60 | client := vb.client 61 | server := client.server 62 | 63 | direct := vt.directCache 64 | fromChannels := vt.fromChannelsCache 65 | 66 | if direct == nil || fromChannels == nil { 67 | direct = make(map[uint32]*Client) 68 | fromChannels = make(map[uint32]*Client) 69 | 70 | for _, vtc := range vt.channels { 71 | channel := server.Channels[int(vtc.id)] 72 | if channel == nil { 73 | continue 74 | } 75 | 76 | if !vtc.subChannels && !vtc.links && vtc.onlyGroup == "" { 77 | if acl.HasPermission(&channel.ACL, client, acl.WhisperPermission) { 78 | for _, target := range channel.clients { 79 | fromChannels[target.Session()] = target 80 | } 81 | } 82 | } else { 83 | server.Printf("%v", vtc) 84 | newchans := make(map[int]*Channel) 85 | if vtc.links { 86 | newchans = channel.AllLinks() 87 | } else { 88 | newchans[channel.Id] = channel 89 | } 90 | if vtc.subChannels { 91 | subchans := channel.AllSubChannels() 92 | for k, v := range subchans { 93 | newchans[k] = v 94 | } 95 | } 96 | for _, newchan := range newchans { 97 | if acl.HasPermission(&newchan.ACL, client, acl.WhisperPermission) { 98 | for _, target := range newchan.clients { 99 | if vtc.onlyGroup == "" || acl.GroupMemberCheck(&newchan.ACL, &newchan.ACL, vtc.onlyGroup, target) { 100 | fromChannels[target.Session()] = target 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | for _, session := range vt.sessions { 109 | target := server.clients[session] 110 | if target != nil { 111 | if _, alreadyInFromChannels := fromChannels[target.Session()]; !alreadyInFromChannels { 112 | direct[target.Session()] = target 113 | } 114 | } 115 | } 116 | 117 | // Make sure we don't send to ourselves. 118 | delete(direct, client.Session()) 119 | delete(fromChannels, client.Session()) 120 | 121 | if vt.directCache == nil { 122 | vt.directCache = direct 123 | } 124 | 125 | if vt.fromChannelsCache == nil { 126 | vt.fromChannelsCache = fromChannels 127 | } 128 | } 129 | 130 | kind := buf[0] & 0xe0 131 | 132 | if len(fromChannels) > 0 { 133 | for _, target := range fromChannels { 134 | buf[0] = kind | 2 135 | err := target.SendUDP(buf) 136 | if err != nil { 137 | target.Panicf("Unable to send UDP packet: %v", err.Error()) 138 | } 139 | } 140 | } 141 | 142 | if len(direct) > 0 { 143 | for _, target := range direct { 144 | buf[0] = kind | 2 145 | target.SendUDP(buf) 146 | err := target.SendUDP(buf) 147 | if err != nil { 148 | target.Panicf("Unable to send UDP packet: %v", err.Error()) 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /pkg/freezer/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // Package freezer implements a persistence layer for Grumble. 6 | package freezer 7 | 8 | // The freezer package exports types that can be persisted to disk, 9 | // both as part of a full server snapshot, and as part of a log of state changes. 10 | // 11 | // The freezer package also implements an append-only log writer that can be used 12 | // to serialize the freezer types to disk in atomic entities called transactions 13 | // records. 14 | // 15 | // A Walker type that can be used to iterate over the different transaction records 16 | // of a log file is also provided. 17 | 18 | import ( 19 | "bytes" 20 | "encoding/binary" 21 | "hash" 22 | "hash/crc32" 23 | "io" 24 | "math" 25 | "os" 26 | 27 | "github.com/golang/protobuf/proto" 28 | ) 29 | 30 | // Log implements an append-only log for flattened 31 | // protobuf-encoded log entries. 32 | // 33 | // These log entries are typically state-change deltas 34 | // for a Grumble server's main data strutures. 35 | // 36 | // The log supports atomic transactions. Transaction groups 37 | // are persisted to disk with a checksum that covers the 38 | // whole transaction group. In case of a failure, none of the 39 | // entries of a transaction will be applied. 40 | type Log struct { 41 | wc io.WriteCloser 42 | } 43 | 44 | // Type LogTx represents a transaction in the log. 45 | // Transactions can be used to group several changes into an 46 | // atomic entity in the log file. 47 | type LogTx struct { 48 | log *Log 49 | crc hash.Hash32 50 | buf *bytes.Buffer 51 | numops int 52 | } 53 | 54 | // Create a new log file 55 | func NewLogFile(fn string) (*Log, error) { 56 | f, err := os.Create(fn) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | log := new(Log) 62 | log.wc = f 63 | 64 | return log, nil 65 | } 66 | 67 | // Close a Log 68 | func (log *Log) Close() error { 69 | return log.wc.Close() 70 | } 71 | 72 | // Append a log entry 73 | // 74 | // This method implicitly creates a transaction 75 | // group for this single Put operation. It is merely 76 | // a convenience wrapper. 77 | func (log *Log) Put(value interface{}) (err error) { 78 | tx := log.BeginTx() 79 | err = tx.Put(value) 80 | if err != nil { 81 | return err 82 | } 83 | return tx.Commit() 84 | } 85 | 86 | // Begin a transaction 87 | func (log *Log) BeginTx() *LogTx { 88 | tx := &LogTx{} 89 | tx.log = log 90 | tx.buf = new(bytes.Buffer) 91 | tx.crc = crc32.NewIEEE() 92 | return tx 93 | } 94 | 95 | // Append a log entry to the transaction. 96 | // The transaction's log entries will not be persisted to 97 | // the log until the Commit has been called on the transaction. 98 | func (tx *LogTx) Put(value interface{}) (err error) { 99 | var ( 100 | buf []byte 101 | kind typeKind 102 | ) 103 | 104 | if tx.numops > 255 { 105 | return ErrTxGroupFull 106 | } 107 | 108 | switch val := value.(type) { 109 | case *Server: 110 | kind = ServerType 111 | buf, err = proto.Marshal(val) 112 | case *ConfigKeyValuePair: 113 | kind = ConfigKeyValuePairType 114 | buf, err = proto.Marshal(val) 115 | case *BanList: 116 | kind = BanListType 117 | buf, err = proto.Marshal(val) 118 | case *User: 119 | kind = UserType 120 | buf, err = proto.Marshal(val) 121 | case *UserRemove: 122 | kind = UserRemoveType 123 | buf, err = proto.Marshal(val) 124 | case *Channel: 125 | kind = ChannelType 126 | buf, err = proto.Marshal(val) 127 | case *ChannelRemove: 128 | kind = ChannelRemoveType 129 | buf, err = proto.Marshal(val) 130 | default: 131 | panic("Attempt to put an unknown type") 132 | } 133 | 134 | if err != nil { 135 | return err 136 | } 137 | 138 | if len(buf) > math.MaxUint16 { 139 | return ErrTxGroupValueTooBig 140 | } 141 | 142 | w := io.MultiWriter(tx.buf, tx.crc) 143 | 144 | err = binary.Write(w, binary.LittleEndian, uint16(kind)) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | err = binary.Write(w, binary.LittleEndian, uint16(len(buf))) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | _, err = w.Write(buf) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | tx.numops += 1 160 | 161 | return nil 162 | } 163 | 164 | // Commit all changes of the transaction to the log 165 | // as a single atomic entry. 166 | func (tx *LogTx) Commit() (err error) { 167 | buf := new(bytes.Buffer) 168 | 169 | err = binary.Write(buf, binary.LittleEndian, uint32(4+4+tx.buf.Len())) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | err = binary.Write(buf, binary.LittleEndian, uint32(tx.numops)) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | err = binary.Write(buf, binary.LittleEndian, tx.crc.Sum32()) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | _, err = buf.Write(tx.buf.Bytes()) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | _, err = tx.log.wc.Write(buf.Bytes()) 190 | if err != nil { 191 | return err 192 | } 193 | 194 | return nil 195 | } 196 | -------------------------------------------------------------------------------- /pkg/cryptstate/cryptstate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package cryptstate 6 | 7 | import ( 8 | "bytes" 9 | "crypto/aes" 10 | "encoding/hex" 11 | "testing" 12 | ) 13 | 14 | func TestOCB2AES128Encrypt(t *testing.T) { 15 | msg := [15]byte{ 16 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 17 | } 18 | key := [aes.BlockSize]byte{ 19 | 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, 20 | } 21 | eiv := [aes.BlockSize]byte{ 22 | 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, 23 | } 24 | div := [aes.BlockSize]byte{ 25 | 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, 26 | } 27 | expected := [19]byte{ 28 | 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, 29 | } 30 | expected_eiv := [aes.BlockSize]byte{ 31 | 0x1f, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, 32 | } 33 | 34 | cs := CryptState{} 35 | out := make([]byte, 19) 36 | cs.SetKey("OCB2-AES128", key[:], eiv[:], div[:]) 37 | cs.Encrypt(out, msg[:]) 38 | 39 | if !bytes.Equal(out[:], expected[:]) { 40 | t.Errorf("Mismatch in output") 41 | } 42 | 43 | if !bytes.Equal(cs.EncryptIV[:], expected_eiv[:]) { 44 | t.Errorf("EIV mismatch") 45 | } 46 | } 47 | 48 | func TestOCB2AES128Decrypt(t *testing.T) { 49 | key := [aes.BlockSize]byte{ 50 | 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, 51 | } 52 | eiv := [aes.BlockSize]byte{ 53 | 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, 54 | } 55 | div := [aes.BlockSize]byte{ 56 | 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, 57 | } 58 | crypted := [19]byte{ 59 | 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, 60 | } 61 | expected := [15]byte{ 62 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 63 | } 64 | post_div := [aes.BlockSize]byte{ 65 | 0x1f, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, 66 | } 67 | 68 | cs := CryptState{} 69 | out := make([]byte, 15) 70 | cs.SetKey("OCB2-AES128", key[:], div[:], eiv[:]) 71 | err := cs.Decrypt(out, crypted[:]) 72 | if err != nil { 73 | t.Fatalf("%v", err) 74 | } 75 | 76 | if !bytes.Equal(out, expected[:]) { 77 | t.Errorf("Mismatch in output") 78 | } 79 | 80 | if !bytes.Equal(cs.DecryptIV, post_div[:]) { 81 | t.Errorf("Mismatch in DIV") 82 | } 83 | } 84 | 85 | // Test that our wrapped NaCl secretbox cipher 86 | // works. The test data for this test was lifted 87 | // from the secretbox_test.go file. 88 | func TestXSalsa20Poly1305Encrypt(t *testing.T) { 89 | cs := CryptState{} 90 | 91 | var key [32]byte 92 | var eiv [24]byte 93 | var div [24]byte 94 | var message [64]byte 95 | 96 | for i := range key[:] { 97 | key[i] = 1 98 | } 99 | 100 | // Since we pre-increment our EIV, 101 | // this look a bit off compared to 102 | // the secretbox_test.go test case. 103 | for i := range eiv[:] { 104 | eiv[i] = 2 105 | div[i] = 2 106 | } 107 | eiv[0] = 1 108 | div[0] = 1 109 | 110 | for i := range message[:] { 111 | message[i] = 3 112 | } 113 | 114 | cs.SetKey("XSalsa20-Poly1305", key[:], div[:], eiv[:]) 115 | dst := make([]byte, len(message)+cs.Overhead()) 116 | cs.Encrypt(dst, message[:]) 117 | 118 | expected, _ := hex.DecodeString("8442bc313f4626f1359e3b50122b6ce6fe66ddfe7d39d14e637eb4fd5b45beadab55198df6ab5368439792a23c87db70acb6156dc5ef957ac04f6276cf6093b84be77ff0849cc33e34b7254d5a8f65ad") 119 | if !bytes.Equal(dst[1:], expected) { 120 | t.Fatalf("mismatch! got\n%x\n, expected\n%x", dst, expected) 121 | } 122 | } 123 | 124 | // Test that we can reverse the result of the Encrypt test. 125 | func TestXSalsa20Poly1305Decrypt(t *testing.T) { 126 | cs := CryptState{} 127 | 128 | var key [32]byte 129 | var eiv [24]byte 130 | var div [24]byte 131 | var expected [64]byte 132 | 133 | for i := range key[:] { 134 | key[i] = 1 135 | } 136 | 137 | // Since we pre-increment our EIV, 138 | // this look a bit off compared to 139 | // the secretbox_test.go test case. 140 | for i := range eiv[:] { 141 | eiv[i] = 2 142 | div[i] = 2 143 | } 144 | eiv[0] = 1 145 | div[0] = 1 146 | 147 | for i := range expected[:] { 148 | expected[i] = 3 149 | } 150 | 151 | message, _ := hex.DecodeString("028442bc313f4626f1359e3b50122b6ce6fe66ddfe7d39d14e637eb4fd5b45beadab55198df6ab5368439792a23c87db70acb6156dc5ef957ac04f6276cf6093b84be77ff0849cc33e34b7254d5a8f65ad") 152 | cs.SetKey("XSalsa20-Poly1305", key[:], eiv[:], div[:]) 153 | dst := make([]byte, len(message)-cs.Overhead()) 154 | err := cs.Decrypt(dst, message[:]) 155 | if err != nil { 156 | t.Fatalf("%v", err) 157 | } 158 | 159 | if !bytes.Equal(dst, expected[:]) { 160 | t.Fatalf("mismatch! got\n%x\n, expected\n%x", dst, expected) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /pkg/acl/acl.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2013 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package acl 6 | 7 | const ( 8 | // Per-channel permissions 9 | NonePermission = 0x0 10 | WritePermission = 0x1 11 | TraversePermission = 0x2 12 | EnterPermission = 0x4 13 | SpeakPermission = 0x8 14 | MuteDeafenPermission = 0x10 15 | MovePermission = 0x20 16 | MakeChannelPermission = 0x40 17 | LinkChannelPermission = 0x80 18 | WhisperPermission = 0x100 19 | TextMessagePermission = 0x200 20 | TempChannelPermission = 0x400 21 | 22 | // Root channel only 23 | KickPermission = 0x10000 24 | BanPermission = 0x20000 25 | RegisterPermission = 0x40000 26 | SelfRegisterPermission = 0x80000 27 | 28 | // Extra flags 29 | CachedPermission = 0x8000000 30 | AllPermissions = 0xf07ff 31 | ) 32 | 33 | // Permission represents a permission in Mumble's ACL system. 34 | type Permission uint32 35 | 36 | // Check whether the given flags are set on perm 37 | func (perm Permission) isSet(check Permission) bool { 38 | return perm&check == check 39 | } 40 | 41 | // IsCached checks whether the ACL has its cache bit set, 42 | // signalling that it was returned from an ACLCache. 43 | func (perm Permission) IsCached() bool { 44 | return perm.isSet(CachedPermission) 45 | } 46 | 47 | // Clean returns a Permission that has its cache bit cleared. 48 | func (perm Permission) Clean() Permission { 49 | return perm ^ Permission(CachedPermission) 50 | } 51 | 52 | // An ACL as defined in an ACL context. 53 | // An ACL can be defined for either a user or a group. 54 | type ACL struct { 55 | // The user id that this ACL applied to. If this 56 | // field is -1, the ACL is a group ACL. 57 | UserId int 58 | // The group that this ACL applies to. 59 | Group string 60 | 61 | // The ApplyHere flag determines whether the ACL 62 | // should apply to the current channel. 63 | ApplyHere bool 64 | // The ApplySubs flag determines whethr the ACL 65 | // should apply to subchannels. 66 | ApplySubs bool 67 | 68 | // The allowed permission flags. 69 | Allow Permission 70 | // The allowed permission flags. The Deny flags override 71 | // permissions set in Allow. 72 | Deny Permission 73 | } 74 | 75 | // IsUserACL returns true if the ACL is defined for a user, 76 | // as opposed to a group. 77 | func (acl *ACL) IsUserACL() bool { 78 | return acl.UserId != -1 79 | } 80 | 81 | // IsChannelACL returns true if the ACL is defined for a group, 82 | // as opposed to a user. 83 | func (acl *ACL) IsChannelACL() bool { 84 | return !acl.IsUserACL() 85 | } 86 | 87 | // HasPermission checks whether the given user has permission perm in the given context. 88 | // The permission perm must be a single permission and not a combination of permissions. 89 | func HasPermission(ctx *Context, user User, perm Permission) bool { 90 | // We can't check permissions on a nil ctx. 91 | if ctx == nil { 92 | panic("acl: HasPermission got nil context") 93 | } 94 | 95 | // SuperUser can't speak or whisper, but everything else is OK 96 | if user.UserId() == 0 { 97 | if perm == SpeakPermission || perm == WhisperPermission { 98 | return false 99 | } 100 | return true 101 | } 102 | 103 | // Default permissions 104 | defaults := Permission(TraversePermission | EnterPermission | SpeakPermission | WhisperPermission | TextMessagePermission) 105 | granted := defaults 106 | contexts := buildChain(ctx) 107 | origCtx := ctx 108 | 109 | traverse := true 110 | write := false 111 | 112 | for _, ctx := range contexts { 113 | // If the context does not inherit any ACLs, use the default permissions. 114 | if !ctx.InheritACL { 115 | granted = defaults 116 | } 117 | // Iterate through ACLs that are defined on ctx. Note: this does not include 118 | // ACLs that iter has inherited from a parent (unless there is also a group on 119 | // iter with the same name, that changes the permissions a bit!) 120 | for _, acl := range ctx.ACLs { 121 | // Determine whether the ACL applies to user. 122 | // If it is a user ACL and the user id of the ACL 123 | // matches user's id, we're good to go. 124 | // 125 | // If it's a group ACL, we have to parse and interpret 126 | // the group string in the current context to determine 127 | // membership. For that we use GroupMemberCheck. 128 | matchUser := acl.IsUserACL() && acl.UserId == user.UserId() 129 | matchGroup := GroupMemberCheck(origCtx, ctx, acl.Group, user) 130 | if matchUser || matchGroup { 131 | if acl.Allow.isSet(TraversePermission) { 132 | traverse = true 133 | } 134 | if acl.Deny.isSet(TraversePermission) { 135 | traverse = false 136 | } 137 | if acl.Allow.isSet(WritePermission) { 138 | write = true 139 | } 140 | if acl.Deny.isSet(WritePermission) { 141 | write = false 142 | } 143 | if (origCtx == ctx && acl.ApplyHere) || (origCtx != ctx && acl.ApplySubs) { 144 | granted |= acl.Allow 145 | granted &= ^acl.Deny 146 | } 147 | } 148 | } 149 | // If traverse is not set and the user doesn't have write permissions 150 | // on the channel, the user will not have any permissions. 151 | // This is because -traverse removes all permissions, and +write grants 152 | // all permissions. 153 | if !traverse && !write { 154 | granted = NonePermission 155 | break 156 | } 157 | } 158 | 159 | // The +write permission implies all permissions except for +speak and +whisper. 160 | // This means that if the user has WritePermission, we should return true for all 161 | // permissions exccept SpeakPermission and WhisperPermission. 162 | if perm != SpeakPermission && perm != WhisperPermission { 163 | return (granted & (perm | WritePermission)) != NonePermission 164 | } else { 165 | return (granted & perm) != NonePermission 166 | } 167 | 168 | return false 169 | } 170 | -------------------------------------------------------------------------------- /pkg/blobstore/blobstore.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // This package implements a simple disk-persisted content-addressed blobstore. 6 | package blobstore 7 | 8 | import ( 9 | "crypto/sha1" 10 | "encoding/hex" 11 | "errors" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | ) 16 | 17 | var ( 18 | // ErrNoSuchKey signals that a blob with the given key does 19 | // not exist in the BlobStore. 20 | ErrNoSuchKey = errors.New("blobstore: no such key") 21 | 22 | // ErrBadKey signals that the given key is not well formed. 23 | ErrBadKey = errors.New("blobstore: bad key") 24 | ) 25 | 26 | // BlobStore represents a simple disk-persisted content addressible 27 | // blob store that uses the file system for persistence. 28 | // 29 | // Blobs in the blobstore are indexed by their SHA1 hash. 30 | // 31 | // The BlobStore is backed by a directory on the filesystem. This 32 | // directory contains subdirectories which contain keys (SHA1 hashes). 33 | // Each subdirectory is named according to the first hex-encoded byte 34 | // of the keys that subdirectory contains. 35 | // 36 | // For example, a file that has the content 'hello world' will have 37 | // the SHA1 hash '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'. If our 38 | // blobstore's backing directory is called 'blobstore', the blob with 39 | // only 'hello world' in it will be stored as follows: 40 | // 41 | // blobstore/2a/2aae6c35c94fcfb415dbe95f408b9ce91ee846ed 42 | // 43 | // The BlobStore is self-synchronizing, relying on the filesystem 44 | // operations to ensure atomicity. Thus, accessing a single BlobStore 45 | // from multiple goroutines should have no ill side effects. 46 | type BlobStore struct { 47 | dir string 48 | } 49 | 50 | // Open opens an existing BlobStore. The path parameter must 51 | // point to a directory that already exists for correct 52 | // operation, however, the Open function does not check that 53 | // this is the case. 54 | func Open(path string) BlobStore { 55 | return BlobStore{dir: path} 56 | } 57 | 58 | // isValidKey checks whether key is a valid BlobStore key. 59 | func isValidKey(key string) bool { 60 | // SHA1 digests are 40 bytes long when hex-encoded. 61 | if len(key) != 40 { 62 | return false 63 | } 64 | 65 | // Check whether the string is valid hex-encoding. 66 | _, err := hex.DecodeString(key) 67 | if err != nil { 68 | return false 69 | } 70 | 71 | return true 72 | } 73 | 74 | // extractKeyComponents returns the directory and the filename that the 75 | // blob identified by key should be stored under in the BlobStore. 76 | // This function also checks whether the key is valid. If not, it returns 77 | // ErrBadKey. 78 | func extractKeyComponents(key string) (dir string, fn string, err error) { 79 | if !isValidKey(key) { 80 | return "", "", ErrBadKey 81 | } 82 | return key[0:2], key, nil 83 | } 84 | 85 | // Get returns a byte slice containing the contents of 86 | // the blob identified by key. If no such blob is found, 87 | // Get returns ErrNoSuchKey. 88 | func (bs BlobStore) Get(key string) ([]byte, error) { 89 | dir, fn, err := extractKeyComponents(key) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | blobfn := filepath.Join(bs.dir, dir, fn) 95 | f, err := os.Open(blobfn) 96 | if os.IsNotExist(err) { 97 | return nil, ErrNoSuchKey 98 | } else if err != nil { 99 | return nil, err 100 | } 101 | 102 | br, err := newBlobReader(f, key) 103 | if err != nil { 104 | f.Close() 105 | return nil, err 106 | } 107 | defer br.Close() 108 | 109 | buf, err := ioutil.ReadAll(br) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | return buf, nil 115 | } 116 | 117 | // Put puts the contents of blob into the BlobStore. If 118 | // the blob was successfully stored, the returned key can 119 | // be used to retrieve the buf from the BlobStore at a 120 | // later time. 121 | func (bs BlobStore) Put(buf []byte) (key string, err error) { 122 | // Calculate the key for the blob. We can't really delay it more than this, 123 | // since we need to know the key for the blob to check whether it's already on 124 | // disk. 125 | h := sha1.New() 126 | _, err = h.Write(buf) 127 | if err != nil { 128 | return "", err 129 | } 130 | key = hex.EncodeToString(h.Sum(nil)) 131 | 132 | // Get the components that make up the on-disk 133 | // path for the blob. 134 | dir, fn, err := extractKeyComponents(key) 135 | if err != nil { 136 | return "", err 137 | } 138 | 139 | blobdir := filepath.Join(bs.dir, dir) 140 | blobpath := filepath.Join(blobdir, fn) 141 | 142 | // Check if the blob already exists. 143 | _, err = os.Stat(blobpath) 144 | if err == nil { 145 | // The file already exists. Our job is done. 146 | return key, nil 147 | } else if os.IsNotExist(err) { 148 | // The blob does not exist on disk yet. 149 | // Fallthrough. 150 | } else if err != nil { 151 | return "", err 152 | } 153 | 154 | // Ensure that blobdir exist. 155 | err = os.Mkdir(blobdir, 0750) 156 | if err != nil && !os.IsExist(err) { 157 | return "", err 158 | } 159 | 160 | // Create a temporary file to write to. 161 | // 162 | // Once we're done, we can atomically rename the file 163 | // to the correct key. 164 | // 165 | // This method is racy: two callers can attempt to write 166 | // the same blob at the same time. This shouldn't affect 167 | // the consistency of the final blob, but worst case, we've 168 | // done some extra work. 169 | f, err := ioutil.TempFile(blobdir, fn) 170 | if err != nil { 171 | return "", err 172 | } 173 | 174 | tmpfn := f.Name() 175 | _, err = f.Write(buf) 176 | if err != nil { 177 | f.Close() 178 | return "", err 179 | } 180 | 181 | err = f.Sync() 182 | if err != nil { 183 | f.Close() 184 | return "", err 185 | } 186 | 187 | err = f.Close() 188 | if err != nil { 189 | return "", err 190 | } 191 | 192 | err = os.Rename(tmpfn, blobpath) 193 | if err != nil { 194 | os.Remove(tmpfn) 195 | return "", err 196 | } 197 | 198 | return key, nil 199 | } 200 | -------------------------------------------------------------------------------- /pkg/cryptstate/cryptstate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package cryptstate 6 | 7 | import ( 8 | "crypto/rand" 9 | "errors" 10 | "io" 11 | "time" 12 | ) 13 | 14 | const decryptHistorySize = 0x100 15 | 16 | type CryptoMode interface { 17 | NonceSize() int 18 | KeySize() int 19 | Overhead() int 20 | 21 | SetKey([]byte) 22 | Encrypt(dst []byte, src []byte, nonce []byte) 23 | Decrypt(dst []byte, src []byte, nonce []byte) bool 24 | } 25 | 26 | type CryptState struct { 27 | Key []byte 28 | EncryptIV []byte 29 | DecryptIV []byte 30 | 31 | LastGoodTime int64 32 | 33 | Good uint32 34 | Late uint32 35 | Lost uint32 36 | Resync uint32 37 | RemoteGood uint32 38 | RemoteLate uint32 39 | RemoteLost uint32 40 | RemoteResync uint32 41 | 42 | decryptHistory [decryptHistorySize]byte 43 | mode CryptoMode 44 | } 45 | 46 | // SupportedModes returns the list of supported CryptoModes. 47 | func SupportedModes() []string { 48 | return []string{ 49 | "OCB2-AES128", 50 | "XSalsa20-Poly1305", 51 | } 52 | } 53 | 54 | // createMode creates the CryptoMode with the given mode name. 55 | func createMode(mode string) (CryptoMode, error) { 56 | switch mode { 57 | case "OCB2-AES128": 58 | return &ocb2Mode{}, nil 59 | case "XSalsa20-Poly1305": 60 | return &secretBoxMode{}, nil 61 | } 62 | return nil, errors.New("cryptstate: no such CryptoMode") 63 | } 64 | 65 | func (cs *CryptState) GenerateKey(mode string) error { 66 | cm, err := createMode(mode) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | key := make([]byte, cm.KeySize()) 72 | _, err = io.ReadFull(rand.Reader, key) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | cm.SetKey(key) 78 | cs.mode = cm 79 | cs.Key = key 80 | 81 | cs.EncryptIV = make([]byte, cm.NonceSize()) 82 | _, err = io.ReadFull(rand.Reader, cs.EncryptIV) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | cs.DecryptIV = make([]byte, cm.NonceSize()) 88 | _, err = io.ReadFull(rand.Reader, cs.DecryptIV) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func (cs *CryptState) SetKey(mode string, key []byte, eiv []byte, div []byte) error { 97 | cm, err := createMode(mode) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | cm.SetKey(key) 103 | cs.mode = cm 104 | cs.Key = key 105 | 106 | cs.EncryptIV = eiv 107 | cs.DecryptIV = div 108 | 109 | return nil 110 | } 111 | 112 | // Overhead returns the length, in bytes, that a ciphertext 113 | // is longer than a plaintext. 114 | func (cs *CryptState) Overhead() int { 115 | return 1 + cs.mode.Overhead() 116 | } 117 | 118 | func (cs *CryptState) Decrypt(dst, src []byte) error { 119 | if len(src) < cs.Overhead() { 120 | return errors.New("cryptstate: crypted length too short to decrypt") 121 | } 122 | 123 | plain_len := len(src) - cs.Overhead() 124 | if len(dst) < plain_len { 125 | return errors.New("cryptstate: not enough space in dst for plain text") 126 | } 127 | 128 | ivbyte := src[0] 129 | restore := false 130 | lost := 0 131 | late := 0 132 | 133 | saveiv := make([]byte, len(cs.DecryptIV)) 134 | copy(saveiv, cs.DecryptIV) 135 | 136 | if byte(cs.DecryptIV[0]+1) == ivbyte { 137 | // In order as expected 138 | if ivbyte > cs.DecryptIV[0] { 139 | cs.DecryptIV[0] = ivbyte 140 | } else if ivbyte < cs.DecryptIV[0] { 141 | cs.DecryptIV[0] = ivbyte 142 | for i := 1; i < len(cs.DecryptIV); i++ { 143 | cs.DecryptIV[i] += 1 144 | if cs.DecryptIV[i] > 0 { 145 | break 146 | } 147 | } 148 | } else { 149 | return errors.New("cryptstate: invalid ivbyte") 150 | } 151 | } else { 152 | // Out of order or repeat 153 | var diff int 154 | diff = int(ivbyte - cs.DecryptIV[0]) 155 | if diff > 128 { 156 | diff = diff - 256 157 | } else if diff < -128 { 158 | diff = diff + 256 159 | } 160 | 161 | if ivbyte < cs.DecryptIV[0] && diff > -30 && diff < 0 { 162 | // Late packet, but no wraparound 163 | late = 1 164 | lost = -1 165 | cs.DecryptIV[0] = ivbyte 166 | restore = true 167 | } else if ivbyte > cs.DecryptIV[0] && diff > -30 && diff < 0 { 168 | // Last was 0x02, here comes 0xff from last round 169 | late = 1 170 | lost = -1 171 | cs.DecryptIV[0] = ivbyte 172 | for i := 1; i < len(cs.DecryptIV); i++ { 173 | cs.DecryptIV[i] -= 1 174 | if cs.DecryptIV[i] > 0 { 175 | break 176 | } 177 | } 178 | restore = true 179 | } else if ivbyte > cs.DecryptIV[0] && diff > 0 { 180 | // Lost a few packets, but beyond that we're good. 181 | lost = int(ivbyte - cs.DecryptIV[0] - 1) 182 | cs.DecryptIV[0] = ivbyte 183 | } else if ivbyte < cs.DecryptIV[0] && diff > 0 { 184 | // Lost a few packets, and wrapped around 185 | lost = int(256 - int(cs.DecryptIV[0]) + int(ivbyte) - 1) 186 | cs.DecryptIV[0] = ivbyte 187 | for i := 1; i < len(cs.DecryptIV); i++ { 188 | cs.DecryptIV[i] += 1 189 | if cs.DecryptIV[i] > 0 { 190 | break 191 | } 192 | } 193 | } else { 194 | return errors.New("cryptstate: no matching ivbyte") 195 | } 196 | 197 | if cs.decryptHistory[cs.DecryptIV[0]] == cs.DecryptIV[1] { 198 | cs.DecryptIV = saveiv 199 | } 200 | } 201 | 202 | ok := cs.mode.Decrypt(dst, src[1:], cs.DecryptIV) 203 | if !ok { 204 | cs.DecryptIV = saveiv 205 | return errors.New("cryptstate: tag mismatch") 206 | } 207 | 208 | cs.decryptHistory[cs.DecryptIV[0]] = cs.DecryptIV[1] 209 | 210 | if restore { 211 | cs.DecryptIV = saveiv 212 | } 213 | 214 | cs.Good += 1 215 | if late > 0 { 216 | cs.Late += uint32(late) 217 | } else { 218 | cs.Late -= uint32(-late) 219 | } 220 | if lost > 0 { 221 | cs.Lost = uint32(lost) 222 | } else { 223 | cs.Lost = uint32(-lost) 224 | } 225 | 226 | cs.LastGoodTime = time.Now().Unix() 227 | 228 | return nil 229 | } 230 | 231 | func (cs *CryptState) Encrypt(dst, src []byte) { 232 | // First, increase our IV 233 | for i := range cs.EncryptIV { 234 | cs.EncryptIV[i] += 1 235 | if cs.EncryptIV[i] > 0 { 236 | break 237 | } 238 | } 239 | 240 | dst[0] = cs.EncryptIV[0] 241 | cs.mode.Encrypt(dst[1:], src, cs.EncryptIV) 242 | } 243 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= 2 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 3 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 4 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 5 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 6 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 7 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 8 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 9 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 10 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 11 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 12 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 13 | golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= 14 | golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 15 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 16 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 17 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 18 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 19 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 20 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 21 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 22 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 25 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 26 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 27 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 28 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 29 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 30 | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= 31 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 37 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 46 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 47 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 48 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 49 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 51 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 52 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 53 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 54 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 55 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 56 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 57 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 58 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 59 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 60 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 61 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 62 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 63 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 64 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 65 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 66 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 67 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 68 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 69 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 70 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 71 | -------------------------------------------------------------------------------- /pkg/freezer/walker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package freezer 6 | 7 | import ( 8 | "encoding/binary" 9 | "hash" 10 | "hash/crc32" 11 | "io" 12 | "math" 13 | 14 | "github.com/golang/protobuf/proto" 15 | ) 16 | 17 | // Checks whether the error err is an EOF 18 | // error. 19 | func isEOF(err error) bool { 20 | if err == io.EOF || err == io.ErrUnexpectedEOF { 21 | return true 22 | } 23 | return false 24 | } 25 | 26 | // Type Walker implements a method for 27 | // iterating the transaction groups of an 28 | // immutable Log. 29 | type Walker struct { 30 | r io.Reader 31 | } 32 | 33 | // Type txReader imlpements a checksumming reader, intended 34 | // for reading transaction groups of a Log. 35 | // 36 | // Besides auto-checksumming the read content, it also 37 | // keeps track of the amount of consumed bytes. 38 | type txReader struct { 39 | r io.Reader 40 | crc32 hash.Hash32 41 | consumed int 42 | } 43 | 44 | // Create a new txReader for reading a transaction group 45 | // from the log. 46 | func newTxReader(r io.Reader) *txReader { 47 | txr := new(txReader) 48 | txr.r = r 49 | txr.crc32 = crc32.NewIEEE() 50 | return txr 51 | } 52 | 53 | // walkReader's Read method. Reads from walkReader's Reader 54 | // and checksums while reading. 55 | func (txr *txReader) Read(p []byte) (n int, err error) { 56 | n, err = txr.r.Read(p) 57 | if err != nil && err != io.EOF { 58 | return 59 | } 60 | txr.consumed += n 61 | 62 | _, crc32err := txr.crc32.Write(p) 63 | if crc32err != nil { 64 | return n, crc32err 65 | } 66 | 67 | return n, err 68 | } 69 | 70 | // Sum32 returns the IEEE-style CRC32 checksum 71 | // of the data read by the walkReader. 72 | func (txr *txReader) Sum32() uint32 { 73 | return txr.crc32.Sum32() 74 | } 75 | 76 | // Consumed returns the amount of bytes consumed by 77 | // the walkReader. 78 | func (txr *txReader) Consumed() int { 79 | return txr.consumed 80 | } 81 | 82 | // Create a new Walker that iterates over the log entries of a given Reader. 83 | func NewReaderWalker(r io.Reader) (walker *Walker, err error) { 84 | walker = new(Walker) 85 | walker.r = r 86 | return walker, nil 87 | } 88 | 89 | // Next returns the next transaction group in the log as a slice of 90 | // pointers to the protobuf-serialized log entries. 91 | // 92 | // This method will only attempt to serialize types with type identifiers 93 | // that this package knows of. In case an unknown type identifier is found 94 | // in a transaction group, it is silently ignored (it's skipped). 95 | // 96 | // On error, Next returns a nil slice and a non-nil err. 97 | // When the end of the file is reached, Next returns nil, os.EOF. 98 | func (walker *Walker) Next() (entries []interface{}, err error) { 99 | var ( 100 | remainBytes uint32 101 | remainOps uint32 102 | crcsum uint32 103 | kind uint16 104 | length uint16 105 | ) 106 | 107 | err = binary.Read(walker.r, binary.LittleEndian, &remainBytes) 108 | if isEOF(err) { 109 | return nil, io.EOF 110 | } else if err != nil { 111 | return nil, err 112 | } 113 | 114 | if remainBytes < 8 { 115 | return nil, ErrUnexpectedEndOfRecord 116 | } 117 | if remainBytes-8 > math.MaxUint8*math.MaxUint16 { 118 | return nil, ErrRecordTooBig 119 | } 120 | 121 | err = binary.Read(walker.r, binary.LittleEndian, &remainOps) 122 | if isEOF(err) { 123 | return nil, ErrUnexpectedEndOfRecord 124 | } else if err != nil { 125 | return nil, err 126 | } 127 | 128 | err = binary.Read(walker.r, binary.LittleEndian, &crcsum) 129 | if isEOF(err) { 130 | return nil, ErrUnexpectedEndOfRecord 131 | } else if err != nil { 132 | return nil, err 133 | } 134 | 135 | remainBytes -= 8 136 | reader := newTxReader(walker.r) 137 | 138 | for remainOps > 0 { 139 | err = binary.Read(reader, binary.LittleEndian, &kind) 140 | if isEOF(err) { 141 | break 142 | } else if err != nil { 143 | return nil, err 144 | } 145 | 146 | err = binary.Read(reader, binary.LittleEndian, &length) 147 | if isEOF(err) { 148 | break 149 | } else if err != nil { 150 | return nil, err 151 | } 152 | 153 | buf := make([]byte, length) 154 | _, err = io.ReadFull(reader, buf) 155 | if isEOF(err) { 156 | break 157 | } else if err != nil { 158 | return nil, err 159 | } 160 | 161 | switch typeKind(kind) { 162 | case ServerType: 163 | server := &Server{} 164 | err = proto.Unmarshal(buf, server) 165 | if isEOF(err) { 166 | break 167 | } else if err != nil { 168 | return nil, err 169 | } 170 | entries = append(entries, server) 171 | case ConfigKeyValuePairType: 172 | cfg := &ConfigKeyValuePair{} 173 | err = proto.Unmarshal(buf, cfg) 174 | if isEOF(err) { 175 | break 176 | } else if err != nil { 177 | return nil, err 178 | } 179 | entries = append(entries, cfg) 180 | case BanListType: 181 | banlist := &BanList{} 182 | err = proto.Unmarshal(buf, banlist) 183 | if isEOF(err) { 184 | break 185 | } else if err != nil { 186 | return nil, err 187 | } 188 | entries = append(entries, banlist) 189 | case UserType: 190 | user := &User{} 191 | err = proto.Unmarshal(buf, user) 192 | if isEOF(err) { 193 | break 194 | } else if err != nil { 195 | return nil, err 196 | } 197 | entries = append(entries, user) 198 | case UserRemoveType: 199 | userRemove := &UserRemove{} 200 | err = proto.Unmarshal(buf, userRemove) 201 | if isEOF(err) { 202 | break 203 | } else if err != nil { 204 | return nil, err 205 | } 206 | entries = append(entries, userRemove) 207 | case ChannelType: 208 | channel := &Channel{} 209 | err = proto.Unmarshal(buf, channel) 210 | if isEOF(err) { 211 | break 212 | } else if err != nil { 213 | return nil, err 214 | } 215 | entries = append(entries, channel) 216 | case ChannelRemoveType: 217 | channelRemove := &ChannelRemove{} 218 | err = proto.Unmarshal(buf, channelRemove) 219 | if isEOF(err) { 220 | break 221 | } else if err != nil { 222 | return nil, err 223 | } 224 | entries = append(entries, channelRemove) 225 | } 226 | 227 | remainOps -= 1 228 | continue 229 | } 230 | 231 | if isEOF(err) { 232 | return nil, ErrUnexpectedEndOfRecord 233 | } 234 | 235 | if reader.Consumed() != int(remainBytes) { 236 | return nil, ErrRemainingBytesForRecord 237 | } 238 | 239 | if reader.Sum32() != crcsum { 240 | return nil, ErrCRC32Mismatch 241 | } 242 | 243 | return entries, nil 244 | } 245 | -------------------------------------------------------------------------------- /pkg/cryptstate/ocb2/ocb2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package ocb2 6 | 7 | import ( 8 | "bytes" 9 | "crypto/aes" 10 | "encoding/hex" 11 | "testing" 12 | ) 13 | 14 | func MustDecodeHex(s string) []byte { 15 | buf, err := hex.DecodeString(s) 16 | if err != nil { 17 | panic("MustDecodeHex: " + err.Error()) 18 | } 19 | return buf 20 | } 21 | 22 | type ocbVector struct { 23 | Name string 24 | Key string 25 | Nonce string 26 | Header string 27 | PlainText string 28 | CipherText string 29 | Tag string 30 | } 31 | 32 | func (v ocbVector) KeyBytes() []byte { 33 | return MustDecodeHex(v.Key) 34 | } 35 | 36 | func (v ocbVector) NonceBytes() []byte { 37 | return MustDecodeHex(v.Nonce) 38 | } 39 | 40 | func (v ocbVector) PlainTextBytes() []byte { 41 | return MustDecodeHex(v.PlainText) 42 | } 43 | 44 | func (v ocbVector) CipherTextBytes() []byte { 45 | return MustDecodeHex(v.CipherText) 46 | } 47 | 48 | func (v ocbVector) TagBytes() []byte { 49 | return MustDecodeHex(v.Tag) 50 | } 51 | 52 | // ocb128Vectors are the test vectors for OCB-AES128 from 53 | // http://www.cs.ucdavis.edu/~rogaway/papers/draft-krovetz-ocb-00.txt 54 | // 55 | // Note: currently, the vectors with headers are not included in this list 56 | // as this implementation does not implement header authentication. 57 | var ocb128Vectors = []ocbVector{ 58 | { 59 | Name: "OCB2-AES-128-001", 60 | Key: "000102030405060708090A0B0C0D0E0F", 61 | Nonce: "000102030405060708090A0B0C0D0E0F", 62 | PlainText: "", 63 | CipherText: "", 64 | Tag: "BF3108130773AD5EC70EC69E7875A7B0", 65 | }, 66 | { 67 | Name: "OCB2-AES-128-002", 68 | Key: "000102030405060708090A0B0C0D0E0F", 69 | Nonce: "000102030405060708090A0B0C0D0E0F", 70 | PlainText: "0001020304050607", 71 | CipherText: "C636B3A868F429BB", 72 | Tag: "A45F5FDEA5C088D1D7C8BE37CABC8C5C", 73 | }, 74 | { 75 | Name: "OCB2-AES-128-003", 76 | Key: "000102030405060708090A0B0C0D0E0F", 77 | Nonce: "000102030405060708090A0B0C0D0E0F", 78 | PlainText: "000102030405060708090A0B0C0D0E0F", 79 | CipherText: "52E48F5D19FE2D9869F0C4A4B3D2BE57", 80 | Tag: "F7EE49AE7AA5B5E6645DB6B3966136F9", 81 | }, 82 | { 83 | Name: "OCB2-AES-128-003", 84 | Key: "000102030405060708090A0B0C0D0E0F", 85 | Nonce: "000102030405060708090A0B0C0D0E0F", 86 | PlainText: "000102030405060708090A0B0C0D0E0F1011121314151617", 87 | CipherText: "F75D6BC8B4DC8D66B836A2B08B32A636CC579E145D323BEB", 88 | Tag: "A1A50F822819D6E0A216784AC24AC84C", 89 | }, 90 | { 91 | Name: "OCB2-AES-128-004", 92 | Key: "000102030405060708090A0B0C0D0E0F", 93 | Nonce: "000102030405060708090A0B0C0D0E0F", 94 | PlainText: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", 95 | CipherText: "F75D6BC8B4DC8D66B836A2B08B32A636CEC3C555037571709DA25E1BB0421A27", 96 | Tag: "09CA6C73F0B5C6C5FD587122D75F2AA3", 97 | }, 98 | { 99 | Name: "OCB2-AES-128-005", 100 | Key: "000102030405060708090A0B0C0D0E0F", 101 | Nonce: "000102030405060708090A0B0C0D0E0F", 102 | PlainText: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", 103 | CipherText: "F75D6BC8B4DC8D66B836A2B08B32A6369F1CD3C5228D79FD6C267F5F6AA7B231C7DFB9D59951AE9C", 104 | Tag: "9DB0CDF880F73E3E10D4EB3217766688", 105 | }, 106 | } 107 | 108 | func TestTimes2(t *testing.T) { 109 | msg := [aes.BlockSize]byte{ 110 | 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 111 | } 112 | expected := [aes.BlockSize]byte{ 113 | 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7b, 114 | } 115 | 116 | times2(msg[0:]) 117 | if !bytes.Equal(msg[0:], expected[0:]) { 118 | t.Fatalf("times2 produces invalid output: %v, expected: %v", msg, expected) 119 | } 120 | } 121 | 122 | func TestTimes3(t *testing.T) { 123 | msg := [aes.BlockSize]byte{ 124 | 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 125 | } 126 | expected := [aes.BlockSize]byte{ 127 | 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 128 | } 129 | 130 | times3(msg[0:]) 131 | if !bytes.Equal(msg[0:], expected[0:]) { 132 | t.Errorf("times3 produces invalid output: %v, expected: %v", msg, expected) 133 | } 134 | } 135 | 136 | func TestZeros(t *testing.T) { 137 | var msg [aes.BlockSize]byte 138 | zeros(msg[0:]) 139 | for i := 0; i < len(msg); i++ { 140 | if msg[i] != 0 { 141 | t.Fatalf("zeros does not zero slice.") 142 | } 143 | } 144 | } 145 | 146 | func TestXor(t *testing.T) { 147 | msg := [aes.BlockSize]byte{ 148 | 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 149 | } 150 | var out [aes.BlockSize]byte 151 | xor(out[0:], msg[0:], msg[0:]) 152 | for i := 0; i < len(out); i++ { 153 | if out[i] != 0 { 154 | t.Fatalf("XOR broken") 155 | } 156 | } 157 | } 158 | 159 | func TestEncryptOCBAES128Vectors(t *testing.T) { 160 | for _, vector := range ocb128Vectors { 161 | cipher, err := aes.NewCipher(vector.KeyBytes()) 162 | if err != nil { 163 | t.Fatalf("%v", err) 164 | } 165 | 166 | plainText := vector.PlainTextBytes() 167 | cipherText := make([]byte, len(plainText)) 168 | tag := make([]byte, TagSize) 169 | Encrypt(cipher, cipherText, plainText, vector.NonceBytes(), tag) 170 | 171 | expectedCipherText := vector.CipherTextBytes() 172 | if !bytes.Equal(cipherText, expectedCipherText) { 173 | t.Fatalf("expected CipherText %#v, got %#v", expectedCipherText, cipherText) 174 | } 175 | 176 | expectedTag := vector.TagBytes() 177 | if !bytes.Equal(tag, expectedTag) { 178 | t.Fatalf("expected tag %#v, got %#v", expectedTag, tag) 179 | } 180 | } 181 | } 182 | 183 | func TestDecryptOCBAES128Vectors(t *testing.T) { 184 | for _, vector := range ocb128Vectors { 185 | cipher, err := aes.NewCipher(vector.KeyBytes()) 186 | if err != nil { 187 | t.Fatalf("%v", err) 188 | } 189 | 190 | cipherText := vector.CipherTextBytes() 191 | plainText := make([]byte, len(cipherText)) 192 | if Decrypt(cipher, plainText, cipherText, vector.NonceBytes(), vector.TagBytes()) == false { 193 | t.Fatalf("expected decrypt success; got failure. tag mismatch?") 194 | } 195 | 196 | expectedPlainText := vector.PlainTextBytes() 197 | if !bytes.Equal(plainText, expectedPlainText) { 198 | t.Fatalf("expected PlainText %#v, got %#v", expectedPlainText, plainText) 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /cmd/grumble/grumble.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | "regexp" 14 | 15 | "mumble.info/grumble/pkg/blobstore" 16 | "mumble.info/grumble/pkg/logtarget" 17 | ) 18 | 19 | var servers map[int64]*Server 20 | var blobStore blobstore.BlobStore 21 | 22 | func main() { 23 | var err error 24 | 25 | flag.Parse() 26 | if Args.ShowHelp == true { 27 | Usage() 28 | return 29 | } 30 | 31 | // Open the data dir to check whether it exists. 32 | dataDir, err := os.Open(Args.DataDir) 33 | if err != nil { 34 | log.Fatalf("Unable to open data directory (%v): %v", Args.DataDir, err) 35 | return 36 | } 37 | dataDir.Close() 38 | 39 | // Set up logging 40 | logtarget.Default, err = logtarget.OpenFile(Args.LogPath, os.Stderr) 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "Unable to open log file (%v): %v", Args.LogPath, err) 43 | return 44 | } 45 | log.SetPrefix("[G] ") 46 | log.SetFlags(log.LstdFlags | log.Lmicroseconds) 47 | log.SetOutput(logtarget.Default) 48 | log.Printf("Grumble") 49 | log.Printf("Using data directory: %s", Args.DataDir) 50 | 51 | // Open the blobstore. If the directory doesn't 52 | // already exist, create the directory and open 53 | // the blobstore. 54 | // The Open method of the blobstore performs simple 55 | // sanity checking of content of the blob directory, 56 | // and will return an error if something's amiss. 57 | blobDir := filepath.Join(Args.DataDir, "blob") 58 | err = os.Mkdir(blobDir, 0700) 59 | if err != nil && !os.IsExist(err) { 60 | log.Fatalf("Unable to create blob directory (%v): %v", blobDir, err) 61 | } 62 | blobStore = blobstore.Open(blobDir) 63 | 64 | // Check whether we should regenerate the default global keypair 65 | // and corresponding certificate. 66 | // These are used as the default certificate of all virtual servers 67 | // and the SSH admin console, but can be overridden using the "key" 68 | // and "cert" arguments to Grumble. 69 | certFn := filepath.Join(Args.DataDir, "cert.pem") 70 | keyFn := filepath.Join(Args.DataDir, "key.pem") 71 | shouldRegen := false 72 | if Args.RegenKeys { 73 | shouldRegen = true 74 | } else { 75 | // OK. Here's the idea: We check for the existence of the cert.pem 76 | // and key.pem files in the data directory on launch. Although these 77 | // might be deleted later (and this check could be deemed useless), 78 | // it's simply here to be convenient for admins. 79 | hasKey := true 80 | hasCert := true 81 | _, err = os.Stat(certFn) 82 | if err != nil && os.IsNotExist(err) { 83 | hasCert = false 84 | } 85 | _, err = os.Stat(keyFn) 86 | if err != nil && os.IsNotExist(err) { 87 | hasKey = false 88 | } 89 | if !hasCert && !hasKey { 90 | shouldRegen = true 91 | } else if !hasCert || !hasKey { 92 | if !hasCert { 93 | log.Fatal("Grumble could not find its default certificate (cert.pem)") 94 | } 95 | if !hasKey { 96 | log.Fatal("Grumble could not find its default private key (key.pem)") 97 | } 98 | } 99 | } 100 | if shouldRegen { 101 | log.Printf("Generating 4096-bit RSA keypair for self-signed certificate...") 102 | 103 | err := GenerateSelfSignedCert(certFn, keyFn) 104 | if err != nil { 105 | log.Printf("Error: %v", err) 106 | return 107 | } 108 | 109 | log.Printf("Certificate output to %v", certFn) 110 | log.Printf("Private key output to %v", keyFn) 111 | } 112 | 113 | // Should we import data from a Murmur SQLite file? 114 | if SQLiteSupport && len(Args.SQLiteDB) > 0 { 115 | f, err := os.Open(Args.DataDir) 116 | if err != nil { 117 | log.Fatalf("Murmur import failed: %s", err.Error()) 118 | } 119 | defer f.Close() 120 | 121 | names, err := f.Readdirnames(-1) 122 | if err != nil { 123 | log.Fatalf("Murmur import failed: %s", err.Error()) 124 | } 125 | 126 | if !Args.CleanUp && len(names) > 0 { 127 | log.Fatalf("Non-empty datadir. Refusing to import Murmur data.") 128 | } 129 | if Args.CleanUp { 130 | log.Print("Cleaning up existing data directory") 131 | for _, name := range names { 132 | if err := os.RemoveAll(filepath.Join(Args.DataDir, name)); err != nil { 133 | log.Fatalf("Unable to cleanup file: %s", name) 134 | } 135 | } 136 | } 137 | 138 | log.Printf("Importing Murmur data from '%s'", Args.SQLiteDB) 139 | if err = MurmurImport(Args.SQLiteDB); err != nil { 140 | log.Fatalf("Murmur import failed: %s", err.Error()) 141 | } 142 | 143 | log.Printf("Import from Murmur SQLite database succeeded.") 144 | log.Printf("Please restart Grumble to make use of the imported data.") 145 | 146 | return 147 | } 148 | 149 | // Create the servers directory if it doesn't already 150 | // exist. 151 | serversDirPath := filepath.Join(Args.DataDir, "servers") 152 | err = os.Mkdir(serversDirPath, 0700) 153 | if err != nil && !os.IsExist(err) { 154 | log.Fatalf("Unable to create servers directory: %v", err) 155 | } 156 | 157 | // Read all entries of the servers directory. 158 | // We need these to load our virtual servers. 159 | serversDir, err := os.Open(serversDirPath) 160 | if err != nil { 161 | log.Fatalf("Unable to open the servers directory: %v", err.Error()) 162 | } 163 | names, err := serversDir.Readdirnames(-1) 164 | if err != nil { 165 | log.Fatalf("Unable to read file from data directory: %v", err.Error()) 166 | } 167 | // The data dir file descriptor. 168 | err = serversDir.Close() 169 | if err != nil { 170 | log.Fatalf("Unable to close data directory: %v", err.Error()) 171 | return 172 | } 173 | 174 | // Look through the list of files in the data directory, and 175 | // load all virtual servers from disk. 176 | servers = make(map[int64]*Server) 177 | for _, name := range names { 178 | if matched, _ := regexp.MatchString("^[0-9]+$", name); matched { 179 | log.Printf("Loading server %v", name) 180 | s, err := NewServerFromFrozen(name) 181 | if err != nil { 182 | log.Fatalf("Unable to load server: %v", err.Error()) 183 | } 184 | err = s.FreezeToFile() 185 | if err != nil { 186 | log.Fatalf("Unable to freeze server to disk: %v", err.Error()) 187 | } 188 | servers[s.Id] = s 189 | } 190 | } 191 | 192 | // If no servers were found, create the default virtual server. 193 | if len(servers) == 0 { 194 | s, err := NewServer(1) 195 | if err != nil { 196 | log.Fatalf("Couldn't start server: %s", err.Error()) 197 | } 198 | 199 | servers[s.Id] = s 200 | os.Mkdir(filepath.Join(serversDirPath, fmt.Sprintf("%v", 1)), 0750) 201 | err = s.FreezeToFile() 202 | if err != nil { 203 | log.Fatalf("Unable to freeze newly created server to disk: %v", err.Error()) 204 | } 205 | } 206 | 207 | // Launch the servers we found during launch... 208 | for _, server := range servers { 209 | err = server.Start() 210 | if err != nil { 211 | log.Printf("Unable to start server %v: %v", server.Id, err.Error()) 212 | } 213 | } 214 | 215 | // If any servers were loaded, launch the signal 216 | // handler goroutine and sleep... 217 | if len(servers) > 0 { 218 | go SignalHandler() 219 | select {} 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /pkg/freezer/freezer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package freezer 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "hash/crc32" 11 | "io" 12 | "math" 13 | "os" 14 | "testing" 15 | 16 | "github.com/golang/protobuf/proto" 17 | ) 18 | 19 | var testValues []proto.Message = []proto.Message{ 20 | &ConfigKeyValuePair{Key: proto.String("Foo")}, 21 | &BanList{Bans: []*Ban{&Ban{Mask: proto.Uint32(32)}}}, 22 | &User{Id: proto.Uint32(0), Name: proto.String("SuperUser")}, 23 | &UserRemove{Id: proto.Uint32(0)}, 24 | &Channel{Id: proto.Uint32(0), Name: proto.String("RootChannel")}, 25 | &ChannelRemove{Id: proto.Uint32(0)}, 26 | } 27 | 28 | // Generate a byet slice representing an entry in a Tx record 29 | func genTxValue(kind uint16, val []byte) (chunk []byte, crc32sum uint32, err error) { 30 | buf := new(bytes.Buffer) 31 | 32 | err = binary.Write(buf, binary.LittleEndian, kind) 33 | if err != nil { 34 | return nil, 0, err 35 | } 36 | 37 | err = binary.Write(buf, binary.LittleEndian, uint16(len(val))) 38 | if err != nil { 39 | return nil, 0, err 40 | } 41 | 42 | _, err = buf.Write(val) 43 | if err != nil { 44 | return nil, 0, err 45 | } 46 | 47 | summer := crc32.NewIEEE() 48 | _, err = summer.Write(val) 49 | if err != nil { 50 | return nil, 0, err 51 | } 52 | 53 | return buf.Bytes(), summer.Sum32(), nil 54 | } 55 | 56 | // Generate the header of a Tx record 57 | func genTestCaseHeader(chunk []byte, numops uint32, crc32sum uint32) (r io.Reader, err error) { 58 | buf := new(bytes.Buffer) 59 | 60 | err = binary.Write(buf, binary.LittleEndian, uint32(4+4+len(chunk))) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | err = binary.Write(buf, binary.LittleEndian, numops) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | err = binary.Write(buf, binary.LittleEndian, crc32sum) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | _, err = buf.Write(chunk) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return buf, nil 81 | } 82 | 83 | // Test that the Walker and the Writer agree on the 84 | // protocol. 85 | func TestCreation(t *testing.T) { 86 | l, err := NewLogFile("creation.log") 87 | if err != nil { 88 | t.Error(err) 89 | return 90 | } 91 | l.Close() 92 | os.Remove("creation.log") 93 | } 94 | 95 | func TestLogging(t *testing.T) { 96 | l, err := NewLogFile("logging.log") 97 | if err != nil { 98 | t.Error(err) 99 | return 100 | } 101 | defer os.Remove("logging.log") 102 | 103 | for _, val := range testValues { 104 | err = l.Put(val) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | } 109 | 110 | err = l.Close() 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | 115 | f, err := os.Open("logging.log") 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | 120 | walker, err := NewReaderWalker(f) 121 | if err != nil { 122 | t.Error(err) 123 | return 124 | } 125 | 126 | i := 0 127 | for { 128 | entries, err := walker.Next() 129 | if err == io.EOF { 130 | err = f.Close() 131 | if err != nil { 132 | t.Fatal(err) 133 | } 134 | break 135 | } else if err != nil { 136 | t.Error(err) 137 | return 138 | } 139 | if len(entries) != 1 { 140 | t.Error("> 1 entry in log tx") 141 | return 142 | } 143 | val, ok := entries[0].(proto.Message) 144 | if !ok { 145 | t.Fatal("val does not implement proto.Message") 146 | } 147 | if !proto.Equal(val, testValues[i]) { 148 | t.Error("proto message mismatch") 149 | } 150 | i += 1 151 | } 152 | } 153 | 154 | // Check that we correctly catch CRC32 mismatches 155 | func TestCRC32MismatchLog(t *testing.T) { 156 | chunk, _, err := genTxValue(0xff, []byte{0xff, 0xff, 0xff, 0xff, 0xff}) 157 | if err != nil { 158 | t.Error(err) 159 | } 160 | 161 | buf, err := genTestCaseHeader(chunk, 1, 0xcafebabe) 162 | if err != nil { 163 | t.Error(err) 164 | } 165 | 166 | walker, err := NewReaderWalker(buf) 167 | if err != nil { 168 | t.Error(err) 169 | } 170 | 171 | _, err = walker.Next() 172 | if err != ErrCRC32Mismatch { 173 | t.Errorf("exepcted CRC32 mismatch, got %v", err) 174 | } 175 | _, err = walker.Next() 176 | if err != io.EOF { 177 | t.Errorf("expected EOF, got %v", err) 178 | } 179 | } 180 | 181 | // Test that unknown TxGroup values are not attempted to be 182 | // decoded. 183 | func TestUnknownTypeDecode(t *testing.T) { 184 | buf, crc32sum, err := genTxValue(0xfa, []byte{0xfa, 0xfa, 0xfa}) 185 | if err != nil { 186 | t.Error(err) 187 | } 188 | 189 | r, err := genTestCaseHeader(buf, 1, crc32sum) 190 | if err != nil { 191 | t.Error(err) 192 | } 193 | 194 | walker, err := NewReaderWalker(r) 195 | if err != nil { 196 | t.Error(err) 197 | } 198 | 199 | entries, err := walker.Next() 200 | // The bytes above should not decode to anything useful 201 | // (because they have an unknown type kind) 202 | if len(entries) != 0 && err != nil { 203 | t.Errorf("expected empty entries and non-nil err (got %v entries and %v)", len(entries), err) 204 | } 205 | _, err = walker.Next() 206 | if err != io.EOF { 207 | t.Errorf("expected EOF, got %v", err) 208 | } 209 | } 210 | 211 | // Test a TxRecord with some trailing bytes 212 | func TestTrailingBytesTxRecord(t *testing.T) { 213 | buf, _, err := genTxValue(0xfa, []byte{0xff, 0xff, 0xff}) 214 | // Add some trailing bytes to the tx record 215 | buf = append(buf, byte(0xff)) 216 | buf = append(buf, byte(0xff)) 217 | buf = append(buf, byte(0xff)) 218 | 219 | summer := crc32.NewIEEE() 220 | _, err = summer.Write(buf) 221 | if err != nil { 222 | t.Error(err) 223 | } 224 | crc32sum := summer.Sum32() 225 | 226 | r, err := genTestCaseHeader(buf, 1, crc32sum) 227 | if err != nil { 228 | t.Error(err) 229 | } 230 | 231 | walker, err := NewReaderWalker(r) 232 | if err != nil { 233 | t.Error(err) 234 | } 235 | 236 | _, err = walker.Next() 237 | if err != ErrRemainingBytesForRecord { 238 | t.Error(err) 239 | } 240 | 241 | _, err = walker.Next() 242 | if err != io.EOF { 243 | t.Errorf("expected EOF, got %v", err) 244 | } 245 | } 246 | 247 | // Test that we check for TxRecords that are too big. 248 | // A TxRecord can hold 255 entries, and each of those can be 249 | // up to 16KB. 250 | func TestTooBigTxRecord(t *testing.T) { 251 | bigValue := make([]byte, math.MaxUint16*math.MaxUint8+4) 252 | r, err := genTestCaseHeader(bigValue, 1, 0) 253 | if err != nil { 254 | t.Error(err) 255 | } 256 | 257 | walker, err := NewReaderWalker(r) 258 | if err != nil { 259 | t.Error(err) 260 | } 261 | 262 | _, err = walker.Next() 263 | if err != ErrRecordTooBig { 264 | t.Errorf("expected ErrRecordTooBig, got %v", err) 265 | } 266 | } 267 | 268 | // Test that we correctly enforce the 255 entry limit of TxGroups. 269 | func TestTxGroupCapacityEnforcement(t *testing.T) { 270 | l, err := NewLogFile("capacity-enforcement.log") 271 | if err != nil { 272 | t.Error(err) 273 | return 274 | } 275 | defer l.Close() 276 | defer os.Remove("capacity-enforcement.log") 277 | 278 | tx := l.BeginTx() 279 | if err != nil { 280 | t.Error(err) 281 | } 282 | 283 | for i := 0; i <= 255; i++ { 284 | entry := testValues[i%len(testValues)] 285 | err = tx.Put(entry) 286 | if err != nil { 287 | t.Error(err) 288 | } 289 | } 290 | 291 | entry := testValues[0] 292 | err = tx.Put(entry) 293 | if err != ErrTxGroupFull { 294 | t.Error(err) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /pkg/packetdata/packetdata.go: -------------------------------------------------------------------------------- 1 | // Grumble - an implementation of Murmur in Go 2 | // Copyright (c) 2010 The Grumble Authors 3 | // The use of this source code is goverened by a BSD-style 4 | // license that can be found in the LICENSE-file. 5 | 6 | package packetdata 7 | 8 | import ( 9 | "math" 10 | ) 11 | 12 | type PacketData struct { 13 | Buf []byte 14 | offset int 15 | maxsize int 16 | overshoot int 17 | ok bool 18 | } 19 | 20 | func New(buf []byte) (pds *PacketData) { 21 | pds = new(PacketData) 22 | pds.Buf = buf 23 | pds.maxsize = len(buf) 24 | pds.ok = true 25 | return 26 | } 27 | 28 | func (pds *PacketData) IsValid() bool { 29 | return pds.ok 30 | } 31 | 32 | func (pds *PacketData) Skip(skip int) { 33 | if pds.Left() >= skip { 34 | pds.offset += skip 35 | } else { 36 | pds.ok = false 37 | } 38 | } 39 | 40 | // Left returns number of bytes remaining in 41 | // the buffer. 42 | func (pds *PacketData) Left() int { 43 | return int(pds.maxsize - pds.offset) 44 | } 45 | 46 | // Size returns the size of the currently-assembled data 47 | // stream 48 | func (pds *PacketData) Size() int { 49 | return pds.offset 50 | } 51 | 52 | // Get the next byte from the PacketData as a uint64 53 | func (pds *PacketData) next() (ret uint64) { 54 | if pds.offset < pds.maxsize { 55 | ret = uint64(pds.Buf[pds.offset]) 56 | pds.offset += 1 57 | return 58 | } else { 59 | pds.ok = false 60 | } 61 | return 0 62 | } 63 | 64 | // Next8 gets the next byte from the PacketData as a byte (uint8) 65 | func (pds *PacketData) Next8() (ret uint8) { 66 | if pds.offset < pds.maxsize { 67 | ret = uint8(pds.Buf[pds.offset]) 68 | pds.offset += 1 69 | return 70 | } else { 71 | pds.ok = false 72 | } 73 | return 0 74 | } 75 | 76 | // Put a byte (represented in an uint64) into the 77 | // PacketData. 78 | func (pds *PacketData) append(val uint64) { 79 | if val > 0xff { 80 | pds.ok = false 81 | return 82 | } 83 | 84 | if pds.offset < pds.maxsize { 85 | pds.Buf[pds.offset] = byte(val) 86 | pds.offset += 1 87 | } else { 88 | pds.ok = false 89 | pds.overshoot++ 90 | } 91 | 92 | } 93 | 94 | // Add a variably-sized integer to the PacketData. 95 | // The PacketData will figure out the most efficient 96 | // encoding based on the binary representation of the value. 97 | func (pds *PacketData) addVarint(val uint64) { 98 | i := val 99 | 100 | if (i&0x8000000000000000) != 0 && ^i < 0x100000000 { 101 | // Signed number 102 | i = ^i 103 | if i <= 0x3 { 104 | // Short for -1 to -4 105 | pds.append(0xfc | i) 106 | } else { 107 | pds.append(0xf8) 108 | } 109 | } 110 | if i < 0x80 { 111 | // Needs top bit clear 112 | pds.append(i) 113 | } else if i < 0x4000 { 114 | // Needs two top bits clear 115 | pds.append((i >> 8) | 0x80) 116 | pds.append(i & 0xff) 117 | } else if i < 0x10000000 { 118 | // Needs three top bits clear 119 | pds.append((i >> 16) | 0xc0) 120 | pds.append((i >> 8) & 0xff) 121 | pds.append(i & 0xff) 122 | } else if i < 0x100000000 { 123 | // Full 32 bit integer 124 | pds.append(0xf0) 125 | pds.append((i >> 24) & 0xff) 126 | pds.append((i >> 16) & 0xff) 127 | pds.append((i >> 8) & 0xff) 128 | pds.append(i & 0xff) 129 | } else { 130 | // 64 bit val 131 | pds.append(0xf4) 132 | pds.append((i >> 56) & 0xff) 133 | pds.append((i >> 48) & 0xff) 134 | pds.append((i >> 40) & 0xff) 135 | pds.append((i >> 32) & 0xff) 136 | pds.append((i >> 24) & 0xff) 137 | pds.append((i >> 16) & 0xff) 138 | pds.append((i >> 8) & 0xff) 139 | pds.append(i & 0xff) 140 | } 141 | } 142 | 143 | func (pds *PacketData) getVarint() (i uint64) { 144 | v := pds.next() 145 | 146 | if (v & 0x80) == 0x00 { 147 | i = (v & 0x7f) 148 | } else if (v & 0xc0) == 0x80 { 149 | i = (v&0x3f)<<8 | pds.next() 150 | } else if (v & 0xf0) == 0xf0 { 151 | switch v & 0xfc { 152 | case 0xf0: 153 | i = pds.next()<<24 | pds.next()<<16 | pds.next()<<8 | pds.next() 154 | case 0xf4: 155 | i = pds.next()<<56 | pds.next()<<48 | pds.next()<<40 | pds.next()<<32 | pds.next()<<24 | pds.next()<<16 | pds.next()<<8 | pds.next() 156 | case 0xf8: 157 | i = ^pds.getVarint() 158 | case 0xfc: 159 | i = ^(v & 0x03) 160 | default: 161 | pds.ok = false 162 | i = 0 163 | } 164 | } else if (v & 0xf0) == 0xe0 { 165 | i = (v&0x0f)<<24 | pds.next()<<16 | pds.next()<<8 | pds.next() 166 | } else if (v & 0xe0) == 0xc0 { 167 | i = (v&0x1f)<<16 | pds.next()<<8 | pds.next() 168 | } 169 | 170 | return 171 | } 172 | 173 | // GetUint64 reads a uint64 from the PacketData 174 | func (pds *PacketData) GetUint64() uint64 { 175 | return pds.getVarint() 176 | } 177 | 178 | // PutUint64 writes a uint64 to the PacketData 179 | func (pds *PacketData) PutUint64(val uint64) { 180 | pds.addVarint(val) 181 | } 182 | 183 | // GetUint32 reads a uint32 from the PacketData 184 | func (pds *PacketData) GetUint32() uint32 { 185 | return uint32(pds.getVarint()) 186 | } 187 | 188 | // PutUint32 writes a uint32 to the PacketData 189 | func (pds *PacketData) PutUint32(val uint32) { 190 | pds.addVarint(uint64(val)) 191 | } 192 | 193 | // GetUint16 reads a uint16 from the PacketData 194 | func (pds *PacketData) GetUint16() uint16 { 195 | return uint16(pds.getVarint()) 196 | } 197 | 198 | // PutUint16 writes a uint16 to the PacketData 199 | func (pds *PacketData) PutUint16(val uint16) { 200 | pds.addVarint(uint64(val)) 201 | } 202 | 203 | // GetUint8 reads a uint8 from the PacketData 204 | func (pds *PacketData) GetUint8() uint8 { 205 | varint := pds.getVarint() 206 | return uint8(varint) 207 | } 208 | 209 | // PutUint8 writes a uint8 to the PacketData 210 | func (pds *PacketData) PutUint8(val uint8) { 211 | pds.addVarint(uint64(val)) 212 | } 213 | 214 | // GetInt64 reads a int64 from the PacketData 215 | func (pds *PacketData) GetInt64() int64 { 216 | return int64(pds.getVarint()) 217 | } 218 | 219 | // PutInt64 writes a int64 to the PacketData 220 | func (pds *PacketData) PutInt64(val int64) { 221 | pds.addVarint(uint64(val)) 222 | } 223 | 224 | // GetInt32 reads a int32 from the PacketData 225 | func (pds *PacketData) GetInt32() int32 { 226 | return int32(pds.getVarint()) 227 | } 228 | 229 | // PutInt32 writes a int32 to the PacketData 230 | func (pds *PacketData) PutInt32(val int32) { 231 | pds.addVarint(uint64(val)) 232 | } 233 | 234 | // GetInt16 reads a int16 from the PacketData 235 | func (pds *PacketData) GetInt16() int16 { 236 | return int16(pds.getVarint()) 237 | } 238 | 239 | // PutInt16 writes a int16 to the PacketData 240 | func (pds *PacketData) PutInt16(val int16) { 241 | pds.addVarint(uint64(val)) 242 | } 243 | 244 | // GetInt8 reads a int8 from the PacketData 245 | func (pds *PacketData) GetInt8() int8 { 246 | return int8(pds.getVarint()) 247 | } 248 | 249 | // PutInt8 writes a int8 to the PacketData 250 | func (pds *PacketData) PutInt8(val int8) { 251 | pds.addVarint(uint64(val)) 252 | } 253 | 254 | // GetFloat32 reads a float32 from the PacketData 255 | func (pds *PacketData) GetFloat32() float32 { 256 | if pds.Left() < 4 { 257 | pds.ok = false 258 | return 0 259 | } 260 | 261 | var val uint32 262 | 263 | val = uint32(pds.Next8())<<24 | uint32(pds.Next8())<<16 | uint32(pds.Next8())<<8 | uint32(pds.Next8()) 264 | return math.Float32frombits(val) 265 | } 266 | 267 | // PutFloat32 writes a float32 to the PacketData 268 | func (pds *PacketData) PutFloat32(val float32) { 269 | bits := math.Float32bits(val) 270 | pds.append(uint64((bits >> 24) & 0xff)) 271 | pds.append(uint64((bits >> 16) & 0xff)) 272 | pds.append(uint64((bits >> 8) & 0xff)) 273 | pds.append(uint64(bits & 0xff)) 274 | } 275 | 276 | // GetFloat64 reads a float64 from the PacketData. 277 | func (pds *PacketData) GetFloat64() float64 { 278 | if pds.Left() < 8 { 279 | pds.ok = false 280 | return 0 281 | } 282 | 283 | var val uint64 284 | val = uint64(pds.Next8())<<56 | uint64(pds.Next8())<<48 | uint64(pds.Next8())<<40 | uint64(pds.Next8())<<32 | uint64(pds.Next8())<<24 | uint64(pds.Next8())<<16 | uint64(pds.Next8())<<8 | uint64(pds.Next8()) 285 | 286 | return math.Float64frombits(val) 287 | } 288 | 289 | // PutFloat64 writes a float64 to the PacketData 290 | func (pds *PacketData) PutFloat64(val float64) { 291 | bits := math.Float64bits(val) 292 | pds.append((bits >> 56) & 0xff) 293 | pds.append((bits >> 48) & 0xff) 294 | pds.append((bits >> 40) & 0xff) 295 | pds.append((bits >> 32) & 0xff) 296 | pds.append((bits >> 24) & 0xff) 297 | pds.append((bits >> 16) & 0xff) 298 | pds.append((bits >> 8) & 0xff) 299 | pds.append(bits & 0xff) 300 | } 301 | 302 | // Copy a buffer out of the PacketData into dst. 303 | func (pds *PacketData) CopyBytes(dst []byte) { 304 | if pds.Left() >= len(dst) { 305 | if copy(dst, pds.Buf[pds.offset:pds.offset+len(dst)]) != len(dst) { 306 | pds.ok = false 307 | } 308 | } else { 309 | pds.ok = false 310 | } 311 | } 312 | 313 | // Put a buffer src into the PacketData at the 314 | // current offset. 315 | func (pds *PacketData) PutBytes(src []byte) { 316 | if pds.Left() >= len(src) { 317 | if copy(pds.Buf[pds.offset:pds.offset+len(src)], src) != len(src) { 318 | pds.ok = false 319 | } else { 320 | pds.offset += len(src) 321 | } 322 | } else { 323 | pds.overshoot += len(src) - pds.Left() 324 | pds.ok = false 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /pkg/cryptstate/ocb2/ocb2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2012 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | // Package ocb2 implements the version 2 of the OCB authenticated-encryption algorithm. 6 | // OCB2 is specified in http://www.cs.ucdavis.edu/~rogaway/papers/draft-krovetz-ocb-00.txt. 7 | // 8 | // Note that this implementation is limited to block ciphers with a block size of 128 bits. 9 | // 10 | // It should also be noted that OCB's author, Phil Rogaway , holds 11 | // several US patents on the algorithm. This should be considered before using this code 12 | // in your own projects. See OCB's FAQ for more info: 13 | // http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-faq.htm#patent:phil 14 | // 15 | // The Mumble Project has a license to use OCB mode in its BSD licensed code on a royalty 16 | // free basis. 17 | package ocb2 18 | 19 | import ( 20 | "crypto/cipher" 21 | "crypto/subtle" 22 | ) 23 | 24 | const ( 25 | // BlockSize defines the block size that this particular implementation 26 | // of OCB2 is made to work on. 27 | BlockSize = 16 28 | // TagSize specifies the length in bytes of a full OCB2 tag. 29 | // As per the specification, applications may truncate their 30 | // tags to a given length, but advocates that typical applications 31 | // should use a tag length of at least 8 bytes (64 bits). 32 | TagSize = BlockSize 33 | // NonceSize specifies the length in bytes of an OCB2 nonce. 34 | NonceSize = BlockSize 35 | ) 36 | 37 | // zeros fills block with zero bytes. 38 | func zeros(block []byte) { 39 | for i := range block { 40 | block[i] = 0 41 | } 42 | } 43 | 44 | // xor outputs the bitwise exclusive-or of a and b to dst. 45 | func xor(dst []byte, a []byte, b []byte) { 46 | for i := 0; i < BlockSize; i++ { 47 | dst[i] = a[i] ^ b[i] 48 | } 49 | } 50 | 51 | // times2 performs the times2 operation, defined as: 52 | // 53 | // times2(S) 54 | // S << 1 if S[1] = 0, and (S << 1) xor const(bitlength(S)) if S[1] = 1. 55 | // 56 | // where const(n) is defined as 57 | // 58 | // const(n) 59 | // The lexicographically first n-bit string C among all 60 | // strings that have a minimal possible number of "1" 61 | // bits and which name a polynomial x^n + C[1] * 62 | // x^{n-1} + ... + C[n-1] * x^1 + C[n] * x^0 that is 63 | // irreducible over the field with two elements. In 64 | // particular, const(128) = num2str(135, 128). For 65 | // other values of n, refer to a standard table of 66 | // irreducible polynomials [G. Seroussi, 67 | // "Table of low-weight binary irreducible polynomials", 68 | // HP Labs Technical Report HPL-98-135, 1998.]. 69 | // 70 | // and num2str(x, n) is defined as 71 | // 72 | // num2str(x, n) 73 | // The n-bit binary representation of the integer x. 74 | // More formally, the n-bit string S where x = S[1] * 75 | // 2^{n-1} + S[2] * 2^{n-2} + ... + S[n] * 2^{0}. Only 76 | // used when 0 <= x < 2^n. 77 | // 78 | // For our 128-bit block size implementation, this means that 79 | // the xor with const(bitlength(S)) if S[1] = 1 is implemented 80 | // by simply xor'ing the last byte with the number 135 when 81 | // S[1] = 1. 82 | func times2(block []byte) { 83 | carry := (block[0] >> 7) & 0x1 84 | for i := 0; i < BlockSize-1; i++ { 85 | block[i] = (block[i] << 1) | ((block[i+1] >> 7) & 0x1) 86 | } 87 | block[BlockSize-1] = (block[BlockSize-1] << 1) ^ (carry * 135) 88 | } 89 | 90 | // times3 performs the times3 operation, defined as: 91 | // 92 | // times3(S) 93 | // times2(S) xor S 94 | func times3(block []byte) { 95 | carry := (block[0] >> 7) & 0x1 96 | for i := 0; i < BlockSize-1; i++ { 97 | block[i] ^= (block[i] << 1) | ((block[i+1] >> 7) & 0x1) 98 | } 99 | block[BlockSize-1] ^= ((block[BlockSize-1] << 1) ^ (carry * 135)) 100 | } 101 | 102 | // Encrypt encrypts the plaintext src and outputs the corresponding ciphertext into dst. 103 | // Besides outputting a ciphertext into dst, Encrypt also outputs an authentication tag 104 | // of ocb2.TagSize bytes into tag, which should be used to verify the authenticity of the 105 | // message on the receiving side. 106 | // 107 | // To ensure both authenticity and secrecy of messages, each invocation to this function must 108 | // be given an unique nonce of ocb2.NonceSize bytes. The nonce need not be secret (it can be 109 | // a counter), but it needs to be unique. 110 | // 111 | // The block cipher used in function must work on a block size equal to ocb2.BlockSize. 112 | // The tag slice used in this function must have a length equal to ocb2.TagSize. 113 | // The nonce slice used in this function must have a length equal to ocb2.NonceSize. 114 | // If any of the above are violated, Encrypt will panic. 115 | func Encrypt(cipher cipher.Block, dst []byte, src []byte, nonce []byte, tag []byte) { 116 | if cipher.BlockSize() != BlockSize { 117 | panic("ocb2: cipher blocksize is not equal to ocb2.BlockSize") 118 | } 119 | if len(nonce) != NonceSize { 120 | panic("ocb2: nonce length is not equal to ocb2.NonceSize") 121 | } 122 | 123 | var ( 124 | checksum [BlockSize]byte 125 | delta [BlockSize]byte 126 | tmp [BlockSize]byte 127 | pad [BlockSize]byte 128 | calcTag [NonceSize]byte 129 | off int 130 | ) 131 | 132 | cipher.Encrypt(delta[0:], nonce[0:]) 133 | zeros(checksum[0:]) 134 | 135 | remain := len(src) 136 | for remain > BlockSize { 137 | times2(delta[0:]) 138 | xor(tmp[0:], delta[0:], src[off:off+BlockSize]) 139 | cipher.Encrypt(tmp[0:], tmp[0:]) 140 | xor(dst[off:off+BlockSize], delta[0:], tmp[0:]) 141 | xor(checksum[0:], checksum[0:], src[off:off+BlockSize]) 142 | remain -= BlockSize 143 | off += BlockSize 144 | } 145 | 146 | times2(delta[0:]) 147 | zeros(tmp[0:]) 148 | num := remain * 8 149 | tmp[BlockSize-2] = uint8((uint32(num) >> 8) & 0xff) 150 | tmp[BlockSize-1] = uint8(num & 0xff) 151 | xor(tmp[0:], tmp[0:], delta[0:]) 152 | cipher.Encrypt(pad[0:], tmp[0:]) 153 | copied := copy(tmp[0:], src[off:]) 154 | if copied != remain { 155 | panic("ocb2: copy failed") 156 | } 157 | if copy(tmp[copied:], pad[copied:]) != (BlockSize - remain) { 158 | panic("ocb2: copy failed") 159 | } 160 | xor(checksum[0:], checksum[0:], tmp[0:]) 161 | xor(tmp[0:], pad[0:], tmp[0:]) 162 | if copy(dst[off:], tmp[0:]) != remain { 163 | panic("ocb2: copy failed") 164 | } 165 | 166 | times3(delta[0:]) 167 | xor(tmp[0:], delta[0:], checksum[0:]) 168 | cipher.Encrypt(calcTag[0:], tmp[0:]) 169 | copy(tag, calcTag[:]) 170 | } 171 | 172 | // Decrypt takes a ciphertext, a nonce, and a tag as its input and outputs a decrypted 173 | // plaintext (if successful) and a boolean flag that determines whether the function 174 | // successfully decrypted the given ciphertext. 175 | // 176 | // Before using the decrpyted plaintext, the application 177 | // should verify that the computed authentication tag matches the tag that was produced when 178 | // encrypting the message (taking into consideration that OCB tags are allowed to be truncated 179 | // to a length less than ocb.TagSize). 180 | // 181 | // The block cipher used in function must work on a block size equal to ocb2.BlockSize. 182 | // The tag slice used in this function must have a length equal to ocb2.TagSize. 183 | // The nonce slice used in this function must have a length equal to ocb2.NonceSize. 184 | // If any of the above are violated, Encrypt will panic. 185 | func Decrypt(cipher cipher.Block, plain []byte, encrypted []byte, nonce []byte, tag []byte) bool { 186 | if cipher.BlockSize() != BlockSize { 187 | panic("ocb2: cipher blocksize is not equal to ocb2.BlockSize") 188 | } 189 | if len(nonce) != NonceSize { 190 | panic("ocb2: nonce length is not equal to ocb2.NonceSize") 191 | } 192 | 193 | var ( 194 | checksum [BlockSize]byte 195 | delta [BlockSize]byte 196 | tmp [BlockSize]byte 197 | pad [BlockSize]byte 198 | calcTag [NonceSize]byte 199 | off int 200 | ) 201 | 202 | cipher.Encrypt(delta[0:], nonce[0:]) 203 | zeros(checksum[0:]) 204 | 205 | remain := len(encrypted) 206 | for remain > BlockSize { 207 | times2(delta[0:]) 208 | xor(tmp[0:], delta[0:], encrypted[off:off+BlockSize]) 209 | cipher.Decrypt(tmp[0:], tmp[0:]) 210 | xor(plain[off:off+BlockSize], delta[0:], tmp[0:]) 211 | xor(checksum[0:], checksum[0:], plain[off:off+BlockSize]) 212 | off += BlockSize 213 | remain -= BlockSize 214 | } 215 | 216 | times2(delta[0:]) 217 | zeros(tmp[0:]) 218 | num := remain * 8 219 | tmp[BlockSize-2] = uint8((uint32(num) >> 8) & 0xff) 220 | tmp[BlockSize-1] = uint8(num & 0xff) 221 | xor(tmp[0:], tmp[0:], delta[0:]) 222 | cipher.Encrypt(pad[0:], tmp[0:]) 223 | zeros(tmp[0:]) 224 | copied := copy(tmp[0:remain], encrypted[off:off+remain]) 225 | if copied != remain { 226 | panic("ocb2: copy failed") 227 | } 228 | xor(tmp[0:], tmp[0:], pad[0:]) 229 | xor(checksum[0:], checksum[0:], tmp[0:]) 230 | copied = copy(plain[off:off+remain], tmp[0:remain]) 231 | if copied != remain { 232 | panic("ocb2: copy failed") 233 | } 234 | 235 | times3(delta[0:]) 236 | xor(tmp[0:], delta[0:], checksum[0:]) 237 | cipher.Encrypt(calcTag[0:], tmp[0:]) 238 | 239 | // Compare the calculated tag with the expected tag. Truncate 240 | // the computed tag if necessary. 241 | if subtle.ConstantTimeCompare(calcTag[:len(tag)], tag) != 1 { 242 | return false 243 | } 244 | 245 | return true 246 | } 247 | -------------------------------------------------------------------------------- /pkg/acl/group.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2013 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | package acl 5 | 6 | import ( 7 | "log" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // Group represents a Group in an Context. 13 | type Group struct { 14 | // The name of this group 15 | Name string 16 | 17 | // The inherit flag means that this group will inherit group 18 | // members from its parent. 19 | Inherit bool 20 | 21 | // The inheritable flag means that subchannels can 22 | // inherit the members of this group. 23 | Inheritable bool 24 | 25 | // Group adds permissions to these users 26 | Add map[int]bool 27 | // Group removes permissions from these users 28 | Remove map[int]bool 29 | // Temporary add (authenticators) 30 | Temporary map[int]bool 31 | } 32 | 33 | // EmptyGroupWithName creates a new Group with the given name. 34 | func EmptyGroupWithName(name string) Group { 35 | grp := Group{} 36 | grp.Name = name 37 | grp.Add = make(map[int]bool) 38 | grp.Remove = make(map[int]bool) 39 | grp.Temporary = make(map[int]bool) 40 | return grp 41 | } 42 | 43 | // AddContains checks whether the Add set contains id. 44 | func (group *Group) AddContains(id int) (ok bool) { 45 | _, ok = group.Add[id] 46 | return 47 | } 48 | 49 | // AddUsers gets the list of user ids in the Add set. 50 | func (group *Group) AddUsers() []int { 51 | users := []int{} 52 | for uid, _ := range group.Add { 53 | users = append(users, uid) 54 | } 55 | return users 56 | } 57 | 58 | // RemoveContains checks whether the Remove set contains id. 59 | func (group *Group) RemoveContains(id int) (ok bool) { 60 | _, ok = group.Remove[id] 61 | return 62 | } 63 | 64 | // RemoveUsers gets the list of user ids in the Remove set. 65 | func (group *Group) RemoveUsers() []int { 66 | users := []int{} 67 | for uid, _ := range group.Remove { 68 | users = append(users, uid) 69 | } 70 | return users 71 | } 72 | 73 | // TemporaryContains checks whether the Temporary set contains id. 74 | func (group *Group) TemporaryContains(id int) (ok bool) { 75 | _, ok = group.Temporary[id] 76 | return 77 | } 78 | 79 | // MembersInContext gets the set of user id's from the group in the given context. 80 | // This includes group members that have been inherited from an ancestor context. 81 | func (group *Group) MembersInContext(ctx *Context) map[int]bool { 82 | groups := []Group{} 83 | members := map[int]bool{} 84 | 85 | // Walk a group's context chain, starting with the context the group 86 | // is defined on, followed by its parent contexts. 87 | origCtx := ctx 88 | for ctx != nil { 89 | curgroup, ok := ctx.Groups[group.Name] 90 | if ok { 91 | // If the group is not inheritable, and we're looking at an 92 | // ancestor group, we've looked in all the groups we should. 93 | if ctx != origCtx && !curgroup.Inheritable { 94 | break 95 | } 96 | // Add the group to the list of groups to be considered 97 | groups = append([]Group{curgroup}, groups...) 98 | // If this group does not inherit from groups in its ancestors, stop looking 99 | // for more ancestor groups. 100 | if !curgroup.Inherit { 101 | break 102 | } 103 | } 104 | ctx = ctx.Parent 105 | } 106 | 107 | for _, curgroup := range groups { 108 | for uid, _ := range curgroup.Add { 109 | members[uid] = true 110 | } 111 | for uid, _ := range curgroup.Remove { 112 | delete(members, uid) 113 | } 114 | } 115 | 116 | return members 117 | } 118 | 119 | // GroupMemberCheck checks whether a user is a member 120 | // of the group as defined in the given context. 121 | // 122 | // The 'current' context is the context that group 123 | // membership is currently being evaluated for. 124 | // 125 | // The 'acl' context is the context of the ACL that 126 | // that group membership is being evaluated for. 127 | // 128 | // The acl context will always be either equal to 129 | // current, or be an ancestor. 130 | func GroupMemberCheck(current *Context, acl *Context, name string, user User) (ok bool) { 131 | valid := true 132 | invert := false 133 | token := false 134 | hash := false 135 | 136 | // Returns the 'correct' return value considering the value 137 | // of the invert flag. 138 | defer func() { 139 | if valid && invert { 140 | ok = !ok 141 | } 142 | }() 143 | 144 | channel := current 145 | 146 | for { 147 | // Empty group name are not valid. 148 | if len(name) == 0 { 149 | valid = false 150 | return false 151 | } 152 | // Invert 153 | if name[0] == '!' { 154 | invert = true 155 | name = name[1:] 156 | continue 157 | } 158 | // Evaluate in ACL context (not current channel) 159 | if name[0] == '~' { 160 | channel = acl 161 | name = name[1:] 162 | continue 163 | } 164 | // Token 165 | if name[0] == '#' { 166 | token = true 167 | name = name[1:] 168 | continue 169 | } 170 | // Hash 171 | if name[0] == '$' { 172 | hash = true 173 | name = name[1:] 174 | continue 175 | } 176 | break 177 | } 178 | 179 | if token { 180 | // The user is part of this group if the remaining name is part of 181 | // his access token list. The name check is case-insensitive. 182 | for _, token := range user.Tokens() { 183 | if strings.ToLower(name) == strings.ToLower(token) { 184 | return true 185 | } 186 | } 187 | return false 188 | } else if hash { 189 | // The client is part of this group if the remaining name matches the 190 | // client's cert hash. 191 | if strings.ToLower(name) == strings.ToLower(user.CertHash()) { 192 | return true 193 | } 194 | return false 195 | } else if name == "none" { 196 | // None 197 | return false 198 | } else if name == "all" { 199 | // Everyone 200 | return true 201 | } else if name == "auth" { 202 | // The user is part of the auth group is he is authenticated. That is, 203 | // his UserId is >= 0. 204 | return user.UserId() >= 0 205 | } else if name == "strong" { 206 | // The user is part of the strong group if he is authenticated to the server 207 | // via a strong certificate (i.e. non-self-signed, trusted by the server's 208 | // trusted set of root CAs). 209 | log.Printf("GroupMemberCheck: Implement strong certificate matching") 210 | return false 211 | } else if name == "in" { 212 | // Is the user in the currently evaluated channel? 213 | return user.ACLContext() == channel 214 | } else if name == "out" { 215 | // Is the user not in the currently evaluated channel? 216 | return user.ACLContext() != channel 217 | } else if name == "sub" { 218 | // fixme(mkrautz): The sub group implementation below hasn't been thoroughly 219 | // tested yet. It might be a bit buggy! 220 | 221 | // Strip away the "sub," part of the name 222 | name = name[4:] 223 | 224 | mindesc := 1 225 | maxdesc := 1000 226 | minpath := 0 227 | 228 | // Parse the groupname to extract the values we should use 229 | // for minpath (first argument), mindesc (second argument), 230 | // and maxdesc (third argument). 231 | args := strings.SplitN(name, ",", 3) 232 | nargs := len(args) 233 | if nargs == 3 { 234 | if len(args[2]) > 0 { 235 | if result, err := strconv.Atoi(args[2]); err == nil { 236 | maxdesc = result 237 | } 238 | } 239 | } 240 | if nargs >= 2 { 241 | if len(args[1]) > 0 { 242 | if result, err := strconv.Atoi(args[1]); err == nil { 243 | mindesc = result 244 | } 245 | } 246 | } 247 | if nargs >= 1 { 248 | if len(args[0]) > 0 { 249 | if result, err := strconv.Atoi(args[0]); err == nil { 250 | minpath = result 251 | } 252 | } 253 | } 254 | 255 | // Build a context chain starting from the 256 | // user's current context. 257 | userChain := buildChain(user.ACLContext()) 258 | // Build a chain of contexts, starting from 259 | // the 'current' context. This is the context 260 | // that group membership is checked against, 261 | // notwithstanding the ~ group operator. 262 | groupChain := buildChain(current) 263 | 264 | // Find the index of the context that the group 265 | // is currently being evaluated on. This can be 266 | // either the 'acl' context or 'current' context 267 | // depending on the ~ group operator. 268 | cofs := indexOf(groupChain, current) 269 | if cofs == -1 { 270 | valid = false 271 | return false 272 | } 273 | 274 | // Add the first parameter of our sub group to cofs 275 | // to get our base context. 276 | cofs += minpath 277 | // Check that the minpath parameter that was given 278 | // is a valid index for groupChain. 279 | if cofs >= len(groupChain) { 280 | valid = false 281 | return false 282 | } else if cofs < 0 { 283 | cofs = 0 284 | } 285 | 286 | // If our base context is not in the userChain, the 287 | // group does not apply to the user. 288 | if indexOf(userChain, groupChain[cofs]) == -1 { 289 | return false 290 | } 291 | 292 | // Down here, we're certain that the userChain 293 | // includes the base context somewhere in its 294 | // chain. We must now determine if the path depth 295 | // makes the user a member of the group. 296 | mindepth := cofs + mindesc 297 | maxdepth := cofs + maxdesc 298 | pdepth := len(userChain) - 1 299 | return pdepth >= mindepth && pdepth <= maxdepth 300 | 301 | } else { 302 | // Non-magic groups 303 | groups := []Group{} 304 | 305 | iter := channel 306 | for iter != nil { 307 | if group, ok := iter.Groups[name]; ok { 308 | // Skip non-inheritable groups if we're in parents 309 | // of our evaluated context. 310 | if iter != channel && !group.Inheritable { 311 | break 312 | } 313 | // Prepend group 314 | groups = append([]Group{group}, groups...) 315 | // If this group does not inherit from groups in its ancestors, stop looking 316 | // for more ancestor groups. 317 | if !group.Inherit { 318 | break 319 | } 320 | } 321 | iter = iter.Parent 322 | } 323 | 324 | isMember := false 325 | for _, group := range groups { 326 | if group.AddContains(user.UserId()) || group.TemporaryContains(user.UserId()) || group.TemporaryContains(-int(user.Session())) { 327 | isMember = true 328 | } 329 | if group.RemoveContains(user.UserId()) { 330 | isMember = false 331 | } 332 | } 333 | return isMember 334 | } 335 | 336 | return false 337 | } 338 | 339 | // GroupNames gets the list of group names for the given ACL context. 340 | // 341 | // This function walks the through the context chain to figure 342 | // out all groups that affect the given context whilst considering 343 | // group inheritance. 344 | func (ctx *Context) GroupNames() []string { 345 | names := map[string]bool{} 346 | origCtx := ctx 347 | contexts := []*Context{} 348 | 349 | // Walk through the whole context chain and all groups in it. 350 | for _, ctx := range contexts { 351 | for _, group := range ctx.Groups { 352 | // A non-inheritable group in parent. Discard it. 353 | if ctx != origCtx && !group.Inheritable { 354 | delete(names, group.Name) 355 | // An inheritable group. Add it to the list. 356 | } else { 357 | names[group.Name] = true 358 | } 359 | } 360 | } 361 | 362 | // Convert to slice 363 | stringNames := make([]string, 0, len(names)) 364 | for name, ok := range names { 365 | if ok { 366 | stringNames = append(stringNames, name) 367 | } 368 | } 369 | return stringNames 370 | } 371 | -------------------------------------------------------------------------------- /pkg/cryptstate/testgen/CryptState.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2005-2010, Thorvald Natvig 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | - Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | - Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | - Neither the name of the Mumble Developers nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 22 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | /* 32 | * This code implements OCB-AES128. 33 | * In the US, OCB is covered by patents. The inventor has given a license 34 | * to all programs distributed under the GPL. 35 | * Mumble is BSD (revised) licensed, meaning you can use the code in a 36 | * closed-source program. If you do, you'll have to either replace 37 | * OCB with something else or get yourself a license. 38 | */ 39 | 40 | #include "CryptState.h" 41 | 42 | #include 43 | #include 44 | #include 45 | 46 | namespace MumbleClient { 47 | 48 | CryptState::CryptState() { 49 | for (int i = 0; i < 0x100; i++) 50 | decrypt_history[i] = 0; 51 | 52 | bInit = false; 53 | uiGood = uiLate = uiLost = uiResync = 0; 54 | uiRemoteGood = uiRemoteLate = uiRemoteLost = uiRemoteResync = 0; 55 | } 56 | 57 | bool CryptState::isValid() const { 58 | return bInit; 59 | } 60 | 61 | void CryptState::genKey() { 62 | RAND_bytes(raw_key, AES_BLOCK_SIZE); 63 | RAND_bytes(encrypt_iv, AES_BLOCK_SIZE); 64 | RAND_bytes(decrypt_iv, AES_BLOCK_SIZE); 65 | AES_set_encrypt_key(raw_key, 128, &encrypt_key); 66 | AES_set_decrypt_key(raw_key, 128, &decrypt_key); 67 | bInit = true; 68 | } 69 | 70 | void CryptState::setKey(const unsigned char* rkey, const unsigned char* eiv, const unsigned char* div) { 71 | memcpy(raw_key, rkey, AES_BLOCK_SIZE); 72 | memcpy(encrypt_iv, eiv, AES_BLOCK_SIZE); 73 | memcpy(decrypt_iv, div, AES_BLOCK_SIZE); 74 | AES_set_encrypt_key(raw_key, 128, &encrypt_key); 75 | AES_set_decrypt_key(raw_key, 128, &decrypt_key); 76 | bInit = true; 77 | } 78 | 79 | void CryptState::setDecryptIV(const unsigned char* iv) { 80 | memcpy(decrypt_iv, iv, AES_BLOCK_SIZE); 81 | } 82 | 83 | const unsigned char* CryptState::getEncryptIV() const { 84 | return encrypt_iv; 85 | } 86 | 87 | void CryptState::encrypt(const unsigned char* source, unsigned char* dst, unsigned int plain_length) { 88 | unsigned char tag[AES_BLOCK_SIZE]; 89 | 90 | // First, increase our IV. 91 | for (int i = 0; i < AES_BLOCK_SIZE; i++) 92 | if (++encrypt_iv[i]) 93 | break; 94 | 95 | ocb_encrypt(source, dst+4, plain_length, encrypt_iv, tag); 96 | 97 | dst[0] = encrypt_iv[0]; 98 | dst[1] = tag[0]; 99 | dst[2] = tag[1]; 100 | dst[3] = tag[2]; 101 | } 102 | 103 | bool CryptState::decrypt(const unsigned char* source, unsigned char* dst, unsigned int crypted_length) { 104 | if (crypted_length < 4) 105 | return false; 106 | 107 | unsigned int plain_length = crypted_length - 4; 108 | 109 | unsigned char saveiv[AES_BLOCK_SIZE]; 110 | unsigned char ivbyte = source[0]; 111 | bool restore = false; 112 | unsigned char tag[AES_BLOCK_SIZE]; 113 | 114 | int lost = 0; 115 | int late = 0; 116 | 117 | memcpy(saveiv, decrypt_iv, AES_BLOCK_SIZE); 118 | 119 | if (((decrypt_iv[0] + 1) & 0xFF) == ivbyte) { 120 | // In order as expected. 121 | if (ivbyte > decrypt_iv[0]) { 122 | decrypt_iv[0] = ivbyte; 123 | } else if (ivbyte < decrypt_iv[0]) { 124 | decrypt_iv[0] = ivbyte; 125 | for (int i = 1;i < AES_BLOCK_SIZE; i++) 126 | if (++decrypt_iv[i]) 127 | break; 128 | } else { 129 | return false; 130 | } 131 | } else { 132 | // This is either out of order or a repeat. 133 | 134 | int diff = ivbyte - decrypt_iv[0]; 135 | if (diff > 128) 136 | diff = diff-256; 137 | else if (diff < -128) 138 | diff = diff+256; 139 | 140 | if ((ivbyte < decrypt_iv[0]) && (diff > -30) && (diff < 0)) { 141 | // Late packet, but no wraparound. 142 | late = 1; 143 | lost = -1; 144 | decrypt_iv[0] = ivbyte; 145 | restore = true; 146 | } else if ((ivbyte > decrypt_iv[0]) && (diff > -30) && (diff < 0)) { 147 | // Last was 0x02, here comes 0xff from last round 148 | late = 1; 149 | lost = -1; 150 | decrypt_iv[0] = ivbyte; 151 | for (int i = 1; i < AES_BLOCK_SIZE; i++) 152 | if (decrypt_iv[i]--) 153 | break; 154 | restore = true; 155 | } else if ((ivbyte > decrypt_iv[0]) && (diff > 0)) { 156 | // Lost a few packets, but beyond that we're good. 157 | lost = ivbyte - decrypt_iv[0] - 1; 158 | decrypt_iv[0] = ivbyte; 159 | } else if ((ivbyte < decrypt_iv[0]) && (diff > 0)) { 160 | // Lost a few packets, and wrapped around 161 | lost = 256 - decrypt_iv[0] + ivbyte - 1; 162 | decrypt_iv[0] = ivbyte; 163 | for (int i = 1; i < AES_BLOCK_SIZE; i++) 164 | if (++decrypt_iv[i]) 165 | break; 166 | } else { 167 | return false; 168 | } 169 | 170 | if (decrypt_history[decrypt_iv[0]] == decrypt_iv[1]) { 171 | memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); 172 | return false; 173 | } 174 | } 175 | 176 | ocb_decrypt(source + 4, dst, plain_length, decrypt_iv, tag); 177 | 178 | if (memcmp(tag, source + 1, 3) != 0) { 179 | memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); 180 | return false; 181 | } 182 | decrypt_history[decrypt_iv[0]] = decrypt_iv[1]; 183 | 184 | if (restore) 185 | memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); 186 | 187 | uiGood++; 188 | uiLate += late; 189 | uiLost += lost; 190 | 191 | return true; 192 | } 193 | 194 | #if defined(__LP64__) 195 | 196 | #define BLOCKSIZE 2 197 | #define SHIFTBITS 63 198 | typedef uint64_t subblock; 199 | 200 | #ifdef __x86_64__ 201 | static inline uint64_t SWAP64(register uint64_t __in) { register uint64_t __out; __asm__("bswap %q0" : "=r"(__out) : "0"(__in)); return __out; } 202 | #else 203 | #define SWAP64(x) ((static_cast(x) << 56) | \ 204 | ((static_cast(x) << 40) & 0xff000000000000ULL) | \ 205 | ((static_cast(x) << 24) & 0xff0000000000ULL) | \ 206 | ((static_cast(x) << 8) & 0xff00000000ULL) | \ 207 | ((static_cast(x) >> 8) & 0xff000000ULL) | \ 208 | ((static_cast(x) >> 24) & 0xff0000ULL) | \ 209 | ((static_cast(x) >> 40) & 0xff00ULL) | \ 210 | ((static_cast(x) >> 56))) 211 | #endif 212 | 213 | #define SWAPPED(x) SWAP64(x) 214 | 215 | #else 216 | #define BLOCKSIZE 4 217 | #define SHIFTBITS 31 218 | typedef uint32_t subblock; 219 | #define SWAPPED(x) htonl(x) 220 | #endif 221 | 222 | typedef subblock keyblock[BLOCKSIZE]; 223 | 224 | #define HIGHBIT (1<> SHIFTBITS; 234 | for (int i = 0; i < BLOCKSIZE - 1; i++) 235 | block[i] = SWAPPED((SWAPPED(block[i]) << 1) | (SWAPPED(block[i + 1]) >> SHIFTBITS)); 236 | block[BLOCKSIZE - 1] = SWAPPED((SWAPPED(block[BLOCKSIZE - 1]) << 1) ^(carry * 0x87)); 237 | } 238 | 239 | static void inline S3(subblock* block) { 240 | subblock carry = SWAPPED(block[0]) >> SHIFTBITS; 241 | for (int i = 0; i < BLOCKSIZE - 1; i++) 242 | block[i] ^= SWAPPED((SWAPPED(block[i]) << 1) | (SWAPPED(block[i + 1]) >> SHIFTBITS)); 243 | block[BLOCKSIZE - 1] ^= SWAPPED((SWAPPED(block[BLOCKSIZE - 1]) << 1) ^(carry * 0x87)); 244 | } 245 | 246 | static void inline ZERO(keyblock &block) { 247 | for (int i = 0; i < BLOCKSIZE; i++) 248 | block[i] = 0; 249 | } 250 | 251 | #define AESencrypt(src,dst,key) AES_encrypt(reinterpret_cast(src),reinterpret_cast(dst), key); 252 | #define AESdecrypt(src,dst,key) AES_decrypt(reinterpret_cast(src),reinterpret_cast(dst), key); 253 | 254 | void CryptState::ocb_encrypt(const unsigned char* plain, unsigned char* encrypted, unsigned int len, const unsigned char* nonce, unsigned char* tag) { 255 | keyblock checksum, delta, tmp, pad; 256 | 257 | // Initialize 258 | AESencrypt(nonce, delta, &encrypt_key); 259 | ZERO(checksum); 260 | 261 | while (len > AES_BLOCK_SIZE) { 262 | S2(delta); 263 | XOR(tmp, delta, reinterpret_cast(plain)); 264 | AESencrypt(tmp, tmp, &encrypt_key); 265 | XOR(reinterpret_cast(encrypted), delta, tmp); 266 | XOR(checksum, checksum, reinterpret_cast(plain)); 267 | len -= AES_BLOCK_SIZE; 268 | plain += AES_BLOCK_SIZE; 269 | encrypted += AES_BLOCK_SIZE; 270 | } 271 | 272 | S2(delta); 273 | ZERO(tmp); 274 | tmp[BLOCKSIZE - 1] = SWAPPED(len * 8); 275 | XOR(tmp, tmp, delta); 276 | AESencrypt(tmp, pad, &encrypt_key); 277 | memcpy(tmp, plain, len); 278 | memcpy(reinterpret_cast(tmp) + len, reinterpret_cast(pad) + len, AES_BLOCK_SIZE - len); 279 | XOR(checksum, checksum, tmp); 280 | XOR(tmp, pad, tmp); 281 | memcpy(encrypted, tmp, len); 282 | 283 | S3(delta); 284 | XOR(tmp, delta, checksum); 285 | AESencrypt(tmp, tag, &encrypt_key); 286 | } 287 | 288 | void CryptState::ocb_decrypt(const unsigned char* encrypted, unsigned char* plain, unsigned int len, const unsigned char* nonce, unsigned char* tag) { 289 | keyblock checksum, delta, tmp, pad; 290 | 291 | // Initialize 292 | AESencrypt(nonce, delta, &encrypt_key); 293 | ZERO(checksum); 294 | 295 | while (len > AES_BLOCK_SIZE) { 296 | S2(delta); 297 | XOR(tmp, delta, reinterpret_cast(encrypted)); 298 | AESdecrypt(tmp, tmp, &decrypt_key); 299 | XOR(reinterpret_cast(plain), delta, tmp); 300 | XOR(checksum, checksum, reinterpret_cast(plain)); 301 | len -= AES_BLOCK_SIZE; 302 | plain += AES_BLOCK_SIZE; 303 | encrypted += AES_BLOCK_SIZE; 304 | } 305 | 306 | S2(delta); 307 | ZERO(tmp); 308 | tmp[BLOCKSIZE - 1] = SWAPPED(len * 8); 309 | XOR(tmp, tmp, delta); 310 | AESencrypt(tmp, pad, &encrypt_key); 311 | memset(tmp, 0, AES_BLOCK_SIZE); 312 | memcpy(tmp, encrypted, len); 313 | XOR(tmp, tmp, pad); 314 | XOR(checksum, checksum, tmp); 315 | memcpy(plain, tmp, len); 316 | 317 | S3(delta); 318 | XOR(tmp, delta, checksum); 319 | AESencrypt(tmp, tag, &encrypt_key); 320 | } 321 | 322 | } // end namespace MumbleClient 323 | -------------------------------------------------------------------------------- /pkg/freezer/types.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: types.proto 3 | // DO NOT EDIT! 4 | 5 | package freezer 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type Server struct { 17 | Config []*ConfigKeyValuePair `protobuf:"bytes,2,rep,name=config" json:"config,omitempty"` 18 | BanList *BanList `protobuf:"bytes,3,opt,name=ban_list" json:"ban_list,omitempty"` 19 | Channels []*Channel `protobuf:"bytes,4,rep,name=channels" json:"channels,omitempty"` 20 | Users []*User `protobuf:"bytes,5,rep,name=users" json:"users,omitempty"` 21 | XXX_unrecognized []byte `json:"-"` 22 | } 23 | 24 | func (this *Server) Reset() { *this = Server{} } 25 | func (this *Server) String() string { return proto.CompactTextString(this) } 26 | func (*Server) ProtoMessage() {} 27 | 28 | func (this *Server) GetBanList() *BanList { 29 | if this != nil { 30 | return this.BanList 31 | } 32 | return nil 33 | } 34 | 35 | type ConfigKeyValuePair struct { 36 | Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` 37 | Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` 38 | XXX_unrecognized []byte `json:"-"` 39 | } 40 | 41 | func (this *ConfigKeyValuePair) Reset() { *this = ConfigKeyValuePair{} } 42 | func (this *ConfigKeyValuePair) String() string { return proto.CompactTextString(this) } 43 | func (*ConfigKeyValuePair) ProtoMessage() {} 44 | 45 | func (this *ConfigKeyValuePair) GetKey() string { 46 | if this != nil && this.Key != nil { 47 | return *this.Key 48 | } 49 | return "" 50 | } 51 | 52 | func (this *ConfigKeyValuePair) GetValue() string { 53 | if this != nil && this.Value != nil { 54 | return *this.Value 55 | } 56 | return "" 57 | } 58 | 59 | type Ban struct { 60 | Ip []byte `protobuf:"bytes,1,opt,name=ip" json:"ip,omitempty"` 61 | Mask *uint32 `protobuf:"varint,2,opt,name=mask" json:"mask,omitempty"` 62 | Username *string `protobuf:"bytes,3,opt,name=username" json:"username,omitempty"` 63 | CertHash *string `protobuf:"bytes,4,opt,name=cert_hash" json:"cert_hash,omitempty"` 64 | Reason *string `protobuf:"bytes,5,opt,name=reason" json:"reason,omitempty"` 65 | Start *int64 `protobuf:"varint,6,opt,name=start" json:"start,omitempty"` 66 | Duration *uint32 `protobuf:"varint,7,opt,name=duration" json:"duration,omitempty"` 67 | XXX_unrecognized []byte `json:"-"` 68 | } 69 | 70 | func (this *Ban) Reset() { *this = Ban{} } 71 | func (this *Ban) String() string { return proto.CompactTextString(this) } 72 | func (*Ban) ProtoMessage() {} 73 | 74 | func (this *Ban) GetIp() []byte { 75 | if this != nil { 76 | return this.Ip 77 | } 78 | return nil 79 | } 80 | 81 | func (this *Ban) GetMask() uint32 { 82 | if this != nil && this.Mask != nil { 83 | return *this.Mask 84 | } 85 | return 0 86 | } 87 | 88 | func (this *Ban) GetUsername() string { 89 | if this != nil && this.Username != nil { 90 | return *this.Username 91 | } 92 | return "" 93 | } 94 | 95 | func (this *Ban) GetCertHash() string { 96 | if this != nil && this.CertHash != nil { 97 | return *this.CertHash 98 | } 99 | return "" 100 | } 101 | 102 | func (this *Ban) GetReason() string { 103 | if this != nil && this.Reason != nil { 104 | return *this.Reason 105 | } 106 | return "" 107 | } 108 | 109 | func (this *Ban) GetStart() int64 { 110 | if this != nil && this.Start != nil { 111 | return *this.Start 112 | } 113 | return 0 114 | } 115 | 116 | func (this *Ban) GetDuration() uint32 { 117 | if this != nil && this.Duration != nil { 118 | return *this.Duration 119 | } 120 | return 0 121 | } 122 | 123 | type BanList struct { 124 | Bans []*Ban `protobuf:"bytes,1,rep,name=bans" json:"bans,omitempty"` 125 | XXX_unrecognized []byte `json:"-"` 126 | } 127 | 128 | func (this *BanList) Reset() { *this = BanList{} } 129 | func (this *BanList) String() string { return proto.CompactTextString(this) } 130 | func (*BanList) ProtoMessage() {} 131 | 132 | type User struct { 133 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 134 | Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` 135 | Password *string `protobuf:"bytes,3,opt,name=password" json:"password,omitempty"` 136 | CertHash *string `protobuf:"bytes,4,opt,name=cert_hash" json:"cert_hash,omitempty"` 137 | Email *string `protobuf:"bytes,5,opt,name=email" json:"email,omitempty"` 138 | TextureBlob *string `protobuf:"bytes,6,opt,name=texture_blob" json:"texture_blob,omitempty"` 139 | CommentBlob *string `protobuf:"bytes,7,opt,name=comment_blob" json:"comment_blob,omitempty"` 140 | LastChannelId *uint32 `protobuf:"varint,8,opt,name=last_channel_id" json:"last_channel_id,omitempty"` 141 | LastActive *uint64 `protobuf:"varint,9,opt,name=last_active" json:"last_active,omitempty"` 142 | XXX_unrecognized []byte `json:"-"` 143 | } 144 | 145 | func (this *User) Reset() { *this = User{} } 146 | func (this *User) String() string { return proto.CompactTextString(this) } 147 | func (*User) ProtoMessage() {} 148 | 149 | func (this *User) GetId() uint32 { 150 | if this != nil && this.Id != nil { 151 | return *this.Id 152 | } 153 | return 0 154 | } 155 | 156 | func (this *User) GetName() string { 157 | if this != nil && this.Name != nil { 158 | return *this.Name 159 | } 160 | return "" 161 | } 162 | 163 | func (this *User) GetPassword() string { 164 | if this != nil && this.Password != nil { 165 | return *this.Password 166 | } 167 | return "" 168 | } 169 | 170 | func (this *User) GetCertHash() string { 171 | if this != nil && this.CertHash != nil { 172 | return *this.CertHash 173 | } 174 | return "" 175 | } 176 | 177 | func (this *User) GetEmail() string { 178 | if this != nil && this.Email != nil { 179 | return *this.Email 180 | } 181 | return "" 182 | } 183 | 184 | func (this *User) GetTextureBlob() string { 185 | if this != nil && this.TextureBlob != nil { 186 | return *this.TextureBlob 187 | } 188 | return "" 189 | } 190 | 191 | func (this *User) GetCommentBlob() string { 192 | if this != nil && this.CommentBlob != nil { 193 | return *this.CommentBlob 194 | } 195 | return "" 196 | } 197 | 198 | func (this *User) GetLastChannelId() uint32 { 199 | if this != nil && this.LastChannelId != nil { 200 | return *this.LastChannelId 201 | } 202 | return 0 203 | } 204 | 205 | func (this *User) GetLastActive() uint64 { 206 | if this != nil && this.LastActive != nil { 207 | return *this.LastActive 208 | } 209 | return 0 210 | } 211 | 212 | type UserRemove struct { 213 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 214 | XXX_unrecognized []byte `json:"-"` 215 | } 216 | 217 | func (this *UserRemove) Reset() { *this = UserRemove{} } 218 | func (this *UserRemove) String() string { return proto.CompactTextString(this) } 219 | func (*UserRemove) ProtoMessage() {} 220 | 221 | func (this *UserRemove) GetId() uint32 { 222 | if this != nil && this.Id != nil { 223 | return *this.Id 224 | } 225 | return 0 226 | } 227 | 228 | type Channel struct { 229 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 230 | Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` 231 | ParentId *uint32 `protobuf:"varint,3,opt,name=parent_id" json:"parent_id,omitempty"` 232 | Position *int64 `protobuf:"varint,4,opt,name=position" json:"position,omitempty"` 233 | InheritAcl *bool `protobuf:"varint,5,opt,name=inherit_acl" json:"inherit_acl,omitempty"` 234 | Links []uint32 `protobuf:"varint,6,rep,name=links" json:"links,omitempty"` 235 | Acl []*ACL `protobuf:"bytes,7,rep,name=acl" json:"acl,omitempty"` 236 | Groups []*Group `protobuf:"bytes,8,rep,name=groups" json:"groups,omitempty"` 237 | DescriptionBlob *string `protobuf:"bytes,9,opt,name=description_blob" json:"description_blob,omitempty"` 238 | XXX_unrecognized []byte `json:"-"` 239 | } 240 | 241 | func (this *Channel) Reset() { *this = Channel{} } 242 | func (this *Channel) String() string { return proto.CompactTextString(this) } 243 | func (*Channel) ProtoMessage() {} 244 | 245 | func (this *Channel) GetId() uint32 { 246 | if this != nil && this.Id != nil { 247 | return *this.Id 248 | } 249 | return 0 250 | } 251 | 252 | func (this *Channel) GetName() string { 253 | if this != nil && this.Name != nil { 254 | return *this.Name 255 | } 256 | return "" 257 | } 258 | 259 | func (this *Channel) GetParentId() uint32 { 260 | if this != nil && this.ParentId != nil { 261 | return *this.ParentId 262 | } 263 | return 0 264 | } 265 | 266 | func (this *Channel) GetPosition() int64 { 267 | if this != nil && this.Position != nil { 268 | return *this.Position 269 | } 270 | return 0 271 | } 272 | 273 | func (this *Channel) GetInheritAcl() bool { 274 | if this != nil && this.InheritAcl != nil { 275 | return *this.InheritAcl 276 | } 277 | return false 278 | } 279 | 280 | func (this *Channel) GetDescriptionBlob() string { 281 | if this != nil && this.DescriptionBlob != nil { 282 | return *this.DescriptionBlob 283 | } 284 | return "" 285 | } 286 | 287 | type ChannelRemove struct { 288 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 289 | XXX_unrecognized []byte `json:"-"` 290 | } 291 | 292 | func (this *ChannelRemove) Reset() { *this = ChannelRemove{} } 293 | func (this *ChannelRemove) String() string { return proto.CompactTextString(this) } 294 | func (*ChannelRemove) ProtoMessage() {} 295 | 296 | func (this *ChannelRemove) GetId() uint32 { 297 | if this != nil && this.Id != nil { 298 | return *this.Id 299 | } 300 | return 0 301 | } 302 | 303 | type ACL struct { 304 | UserId *uint32 `protobuf:"varint,1,opt,name=user_id" json:"user_id,omitempty"` 305 | Group *string `protobuf:"bytes,2,opt,name=group" json:"group,omitempty"` 306 | ApplyHere *bool `protobuf:"varint,3,opt,name=apply_here" json:"apply_here,omitempty"` 307 | ApplySubs *bool `protobuf:"varint,4,opt,name=apply_subs" json:"apply_subs,omitempty"` 308 | Allow *uint32 `protobuf:"varint,5,opt,name=allow" json:"allow,omitempty"` 309 | Deny *uint32 `protobuf:"varint,6,opt,name=deny" json:"deny,omitempty"` 310 | XXX_unrecognized []byte `json:"-"` 311 | } 312 | 313 | func (this *ACL) Reset() { *this = ACL{} } 314 | func (this *ACL) String() string { return proto.CompactTextString(this) } 315 | func (*ACL) ProtoMessage() {} 316 | 317 | func (this *ACL) GetUserId() uint32 { 318 | if this != nil && this.UserId != nil { 319 | return *this.UserId 320 | } 321 | return 0 322 | } 323 | 324 | func (this *ACL) GetGroup() string { 325 | if this != nil && this.Group != nil { 326 | return *this.Group 327 | } 328 | return "" 329 | } 330 | 331 | func (this *ACL) GetApplyHere() bool { 332 | if this != nil && this.ApplyHere != nil { 333 | return *this.ApplyHere 334 | } 335 | return false 336 | } 337 | 338 | func (this *ACL) GetApplySubs() bool { 339 | if this != nil && this.ApplySubs != nil { 340 | return *this.ApplySubs 341 | } 342 | return false 343 | } 344 | 345 | func (this *ACL) GetAllow() uint32 { 346 | if this != nil && this.Allow != nil { 347 | return *this.Allow 348 | } 349 | return 0 350 | } 351 | 352 | func (this *ACL) GetDeny() uint32 { 353 | if this != nil && this.Deny != nil { 354 | return *this.Deny 355 | } 356 | return 0 357 | } 358 | 359 | type Group struct { 360 | Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 361 | Inherit *bool `protobuf:"varint,2,opt,name=inherit" json:"inherit,omitempty"` 362 | Inheritable *bool `protobuf:"varint,3,opt,name=inheritable" json:"inheritable,omitempty"` 363 | Add []uint32 `protobuf:"varint,4,rep,name=add" json:"add,omitempty"` 364 | Remove []uint32 `protobuf:"varint,5,rep,name=remove" json:"remove,omitempty"` 365 | XXX_unrecognized []byte `json:"-"` 366 | } 367 | 368 | func (this *Group) Reset() { *this = Group{} } 369 | func (this *Group) String() string { return proto.CompactTextString(this) } 370 | func (*Group) ProtoMessage() {} 371 | 372 | func (this *Group) GetName() string { 373 | if this != nil && this.Name != nil { 374 | return *this.Name 375 | } 376 | return "" 377 | } 378 | 379 | func (this *Group) GetInherit() bool { 380 | if this != nil && this.Inherit != nil { 381 | return *this.Inherit 382 | } 383 | return false 384 | } 385 | 386 | func (this *Group) GetInheritable() bool { 387 | if this != nil && this.Inheritable != nil { 388 | return *this.Inheritable 389 | } 390 | return false 391 | } 392 | 393 | func init() { 394 | } 395 | -------------------------------------------------------------------------------- /cmd/grumble/murmurdb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The Grumble Authors 2 | // The use of this source code is goverened by a BSD-style 3 | // license that can be found in the LICENSE-file. 4 | 5 | package main 6 | 7 | // This file implements a Server that can be created from a Murmur SQLite file. 8 | // This is read-only, so it's not generally useful. It's meant as a convenient 9 | // way to import a Murmur server into Grumble, to be able to dump the structure of the 10 | // SQLite datbase into a format that Grumble can understand. 11 | 12 | import ( 13 | "database/sql" 14 | "errors" 15 | "log" 16 | "net" 17 | "os" 18 | "path/filepath" 19 | "strconv" 20 | 21 | "mumble.info/grumble/pkg/acl" 22 | "mumble.info/grumble/pkg/ban" 23 | ) 24 | 25 | const ( 26 | ChannelInfoDescription int = iota 27 | ChannelInfoPosition 28 | ) 29 | 30 | const ( 31 | UserInfoName int = iota 32 | UserInfoEmail 33 | UserInfoComment 34 | UserInfoHash 35 | UserInfoPassword 36 | UserInfoLastActive 37 | ) 38 | 39 | const SQLiteSupport = true 40 | 41 | // Import the structure of an existing Murmur SQLite database. 42 | func MurmurImport(filename string) (err error) { 43 | db, err := sql.Open("sqlite", filename) 44 | if err != nil { 45 | panic(err.Error()) 46 | } 47 | 48 | rows, err := db.Query("SELECT server_id FROM servers") 49 | if err != nil { 50 | panic(err.Error()) 51 | } 52 | 53 | var serverids []int64 54 | var sid int64 55 | for rows.Next() { 56 | err = rows.Scan(&sid) 57 | if err != nil { 58 | return err 59 | } 60 | serverids = append(serverids, sid) 61 | } 62 | 63 | log.Printf("Found servers: %v (%v servers)", serverids, len(serverids)) 64 | 65 | for _, sid := range serverids { 66 | m, err := NewServerFromSQLite(sid, db) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | err = os.Mkdir(filepath.Join(Args.DataDir, strconv.FormatInt(sid, 10)), 0750) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | err = m.FreezeToFile() 77 | if err != nil { 78 | return err 79 | } 80 | 81 | log.Printf("Successfully imported server %v", sid) 82 | } 83 | 84 | return 85 | } 86 | 87 | // Create a new Server from a Murmur SQLite database 88 | func NewServerFromSQLite(id int64, db *sql.DB) (s *Server, err error) { 89 | s, err = NewServer(id) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | err = populateChannelInfoFromDatabase(s, s.RootChannel(), db) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | err = populateChannelACLFromDatabase(s, s.RootChannel(), db) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | err = populateChannelGroupsFromDatabase(s, s.RootChannel(), db) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | err = populateChannelsFromDatabase(s, db, 0) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | err = populateChannelLinkInfo(s, db) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | err = populateUsers(s, db) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | err = populateBans(s, db) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | return 130 | } 131 | 132 | // Add channel metadata (channel_info table from SQLite) by reading the SQLite database. 133 | func populateChannelInfoFromDatabase(server *Server, c *Channel, db *sql.DB) error { 134 | stmt, err := db.Prepare("SELECT value FROM channel_info WHERE server_id=? AND channel_id=? AND key=?") 135 | if err != nil { 136 | return err 137 | } 138 | 139 | // Fetch description 140 | rows, err := stmt.Query(server.Id, c.Id, ChannelInfoDescription) 141 | if err != nil { 142 | return err 143 | } 144 | for rows.Next() { 145 | var description string 146 | err = rows.Scan(&description) 147 | if err != nil { 148 | return err 149 | } 150 | 151 | if len(description) > 0 { 152 | key, err := blobStore.Put([]byte(description)) 153 | if err != nil { 154 | return err 155 | } 156 | c.DescriptionBlob = key 157 | } 158 | } 159 | 160 | // Fetch position 161 | rows, err = stmt.Query(server.Id, c.Id, ChannelInfoPosition) 162 | if err != nil { 163 | return err 164 | } 165 | for rows.Next() { 166 | var pos int 167 | if err := rows.Scan(&pos); err != nil { 168 | return err 169 | } 170 | 171 | c.Position = pos 172 | } 173 | 174 | return nil 175 | } 176 | 177 | // Populate channel with its ACLs by reading the SQLite databse. 178 | func populateChannelACLFromDatabase(server *Server, c *Channel, db *sql.DB) error { 179 | stmt, err := db.Prepare("SELECT user_id, group_name, apply_here, apply_sub, grantpriv, revokepriv FROM acl WHERE server_id=? AND channel_id=? ORDER BY priority") 180 | if err != nil { 181 | return err 182 | } 183 | 184 | rows, err := stmt.Query(server.Id, c.Id) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | for rows.Next() { 190 | var ( 191 | UserId string 192 | Group string 193 | ApplyHere bool 194 | ApplySub bool 195 | Allow int64 196 | Deny int64 197 | ) 198 | if err := rows.Scan(&UserId, &Group, &ApplyHere, &ApplySub, &Allow, &Deny); err != nil { 199 | return err 200 | } 201 | 202 | aclEntry := acl.ACL{} 203 | aclEntry.ApplyHere = ApplyHere 204 | aclEntry.ApplySubs = ApplySub 205 | if len(UserId) > 0 { 206 | aclEntry.UserId, err = strconv.Atoi(UserId) 207 | if err != nil { 208 | return err 209 | } 210 | } else if len(Group) > 0 { 211 | aclEntry.Group = Group 212 | } else { 213 | return errors.New("Invalid ACL: Neither Group or UserId specified") 214 | } 215 | 216 | aclEntry.Deny = acl.Permission(Deny) 217 | aclEntry.Allow = acl.Permission(Allow) 218 | c.ACL.ACLs = append(c.ACL.ACLs, aclEntry) 219 | } 220 | 221 | return nil 222 | } 223 | 224 | // Populate channel with groups by reading the SQLite database. 225 | func populateChannelGroupsFromDatabase(server *Server, c *Channel, db *sql.DB) error { 226 | stmt, err := db.Prepare("SELECT group_id, name, inherit, inheritable FROM groups WHERE server_id=? AND channel_id=?") 227 | if err != nil { 228 | return err 229 | } 230 | 231 | rows, err := stmt.Query(server.Id, c.Id) 232 | if err != nil { 233 | return err 234 | } 235 | 236 | groups := make(map[int64]acl.Group) 237 | 238 | for rows.Next() { 239 | var ( 240 | GroupId int64 241 | Name string 242 | Inherit bool 243 | Inheritable bool 244 | ) 245 | 246 | if err := rows.Scan(&GroupId, &Name, &Inherit, &Inheritable); err != nil { 247 | return err 248 | } 249 | 250 | g := acl.EmptyGroupWithName(Name) 251 | g.Inherit = Inherit 252 | g.Inheritable = Inheritable 253 | c.ACL.Groups[g.Name] = g 254 | groups[GroupId] = g 255 | } 256 | 257 | stmt, err = db.Prepare("SELECT user_id, addit FROM group_members WHERE server_id=? AND group_id=?") 258 | if err != nil { 259 | return err 260 | } 261 | 262 | for gid, grp := range groups { 263 | rows, err = stmt.Query(server.Id, gid) 264 | if err != nil { 265 | return err 266 | } 267 | 268 | for rows.Next() { 269 | var ( 270 | UserId int64 271 | Add bool 272 | ) 273 | 274 | if err := rows.Scan(&UserId, &Add); err != nil { 275 | return err 276 | } 277 | 278 | if Add { 279 | grp.Add[int(UserId)] = true 280 | } else { 281 | grp.Remove[int(UserId)] = true 282 | } 283 | } 284 | } 285 | 286 | return nil 287 | } 288 | 289 | // Populate the Server with Channels from the database. 290 | func populateChannelsFromDatabase(server *Server, db *sql.DB, parentId int) error { 291 | parent, exists := server.Channels[parentId] 292 | if !exists { 293 | return errors.New("Non-existant parent") 294 | } 295 | 296 | stmt, err := db.Prepare("SELECT channel_id, name, inheritacl FROM channels WHERE server_id=? AND parent_id=?") 297 | if err != nil { 298 | return err 299 | } 300 | 301 | rows, err := stmt.Query(server.Id, parentId) 302 | if err != nil { 303 | return err 304 | } 305 | 306 | for rows.Next() { 307 | var ( 308 | name string 309 | chanid int 310 | inherit bool 311 | ) 312 | err = rows.Scan(&chanid, &name, &inherit) 313 | if err != nil { 314 | return err 315 | } 316 | 317 | c := NewChannel(chanid, name) 318 | server.Channels[c.Id] = c 319 | c.ACL.InheritACL = inherit 320 | parent.AddChild(c) 321 | } 322 | 323 | // Add channel_info 324 | for _, c := range parent.children { 325 | err = populateChannelInfoFromDatabase(server, c, db) 326 | if err != nil { 327 | return err 328 | } 329 | } 330 | 331 | // Add ACLs 332 | for _, c := range parent.children { 333 | err = populateChannelACLFromDatabase(server, c, db) 334 | if err != nil { 335 | return err 336 | } 337 | } 338 | 339 | // Add groups 340 | for _, c := range parent.children { 341 | err = populateChannelGroupsFromDatabase(server, c, db) 342 | if err != nil { 343 | return err 344 | } 345 | } 346 | 347 | // Add subchannels 348 | for id, _ := range parent.children { 349 | err = populateChannelsFromDatabase(server, db, id) 350 | if err != nil { 351 | return err 352 | } 353 | } 354 | 355 | return nil 356 | } 357 | 358 | // Link a Server's channels together 359 | func populateChannelLinkInfo(server *Server, db *sql.DB) (err error) { 360 | stmt, err := db.Prepare("SELECT channel_id, link_id FROM channel_links WHERE server_id=?") 361 | if err != nil { 362 | return err 363 | } 364 | 365 | rows, err := stmt.Query(server.Id) 366 | if err != nil { 367 | return err 368 | } 369 | 370 | for rows.Next() { 371 | var ( 372 | ChannelId int 373 | LinkId int 374 | ) 375 | if err := rows.Scan(&ChannelId, &LinkId); err != nil { 376 | return err 377 | } 378 | 379 | channel, exists := server.Channels[ChannelId] 380 | if !exists { 381 | return errors.New("Attempt to perform link operation on non-existant channel.") 382 | } 383 | 384 | other, exists := server.Channels[LinkId] 385 | if !exists { 386 | return errors.New("Attempt to perform link operation on non-existant channel.") 387 | } 388 | 389 | server.LinkChannels(channel, other) 390 | } 391 | 392 | return nil 393 | } 394 | 395 | func populateUsers(server *Server, db *sql.DB) (err error) { 396 | // Populate the server with regular user data 397 | stmt, err := db.Prepare("SELECT user_id, name, pw, lastchannel, texture, strftime('%s', last_active) FROM users WHERE server_id=?") 398 | if err != nil { 399 | return 400 | } 401 | 402 | rows, err := stmt.Query(server.Id) 403 | if err != nil { 404 | return 405 | } 406 | 407 | for rows.Next() { 408 | var ( 409 | UserId int64 410 | UserName string 411 | SHA1Password string 412 | LastChannel int 413 | Texture []byte 414 | LastActive int64 415 | ) 416 | 417 | err = rows.Scan(&UserId, &UserName, &SHA1Password, &LastChannel, &Texture, &LastActive) 418 | if err != nil { 419 | continue 420 | } 421 | 422 | if UserId == 0 { 423 | server.cfg.Set("SuperUserPassword", "sha1$$"+SHA1Password) 424 | } 425 | 426 | user, err := NewUser(uint32(UserId), UserName) 427 | if err != nil { 428 | return err 429 | } 430 | 431 | if len(Texture) > 0 { 432 | key, err := blobStore.Put(Texture) 433 | if err != nil { 434 | return err 435 | } 436 | user.TextureBlob = key 437 | } 438 | 439 | user.LastActive = uint64(LastActive) 440 | user.LastChannelId = LastChannel 441 | 442 | server.Users[user.Id] = user 443 | } 444 | 445 | stmt, err = db.Prepare("SELECT key, value FROM user_info WHERE server_id=? AND user_id=?") 446 | if err != nil { 447 | return 448 | } 449 | 450 | // Populate users with any new-style UserInfo records 451 | for uid, user := range server.Users { 452 | rows, err = stmt.Query(server.Id, uid) 453 | if err != nil { 454 | return err 455 | } 456 | 457 | for rows.Next() { 458 | var ( 459 | Key int 460 | Value string 461 | ) 462 | 463 | err = rows.Scan(&Key, &Value) 464 | if err != nil { 465 | return err 466 | } 467 | 468 | switch Key { 469 | case UserInfoEmail: 470 | user.Email = Value 471 | case UserInfoComment: 472 | key, err := blobStore.Put([]byte(Value)) 473 | if err != nil { 474 | return err 475 | } 476 | user.CommentBlob = key 477 | case UserInfoHash: 478 | user.CertHash = Value 479 | case UserInfoLastActive: 480 | // not a kv-pair (trigger) 481 | case UserInfoPassword: 482 | // not a kv-pair 483 | case UserInfoName: 484 | // not a kv-pair 485 | } 486 | } 487 | } 488 | 489 | return 490 | } 491 | 492 | // Populate bans 493 | func populateBans(server *Server, db *sql.DB) (err error) { 494 | stmt, err := db.Prepare("SELECT base, mask, name, hash, reason, start, duration FROM bans WHERE server_id=?") 495 | if err != nil { 496 | return 497 | } 498 | 499 | rows, err := stmt.Query(server.Id) 500 | if err != nil { 501 | return err 502 | } 503 | 504 | for rows.Next() { 505 | var ( 506 | Ban ban.Ban 507 | IP []byte 508 | StartDate string 509 | Duration int64 510 | ) 511 | 512 | err = rows.Scan(&IP, &Ban.Mask, &Ban.Username, &Ban.CertHash, &Ban.Reason, &StartDate, &Duration) 513 | if err != nil { 514 | return err 515 | } 516 | 517 | if len(IP) == 16 && IP[10] == 0xff && IP[11] == 0xff { 518 | Ban.IP = net.IPv4(IP[12], IP[13], IP[14], IP[15]) 519 | } else { 520 | Ban.IP = IP 521 | } 522 | 523 | Ban.SetISOStartDate(StartDate) 524 | Ban.Duration = uint32(Duration) 525 | 526 | server.Bans = append(server.Bans, Ban) 527 | } 528 | 529 | return 530 | } 531 | --------------------------------------------------------------------------------