From 6eeea854be6bea74320bf47c5e666a56a924b05f Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 7 Nov 2024 12:14:45 +0100 Subject: [PATCH 1/5] storage/filesystem: add R/W locking This commit adds read/write locking for individual files, so that concurrent requests (e.g. to read and write the same file) cannot interfere with one another. The locking is not very fine-grained at the moment, and can probably be improved upon. But it does ensure consistency. --- storage/filesystem.go | 35 +++++++++++++++++++++++++++++++++++ storage/filesystem_caldav.go | 27 ++++++++++++++++++++++++++- storage/filesystem_carddav.go | 28 +++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/storage/filesystem.go b/storage/filesystem.go index afc058b..c6f3887 100644 --- a/storage/filesystem.go +++ b/storage/filesystem.go @@ -10,6 +10,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "github.com/rs/zerolog/log" @@ -20,9 +21,13 @@ import ( type filesystemBackend struct { webdav.UserPrincipalBackend + path string caldavPrefix string carddavPrefix string + + // maps file path to *sync.RWMutex + locks sync.Map } var ( @@ -47,6 +52,36 @@ func NewFilesystem(fsPath, caldavPrefix, carddavPrefix string, userPrincipalBack return backend, backend, nil } +func (b *filesystemBackend) RLock(filename string) { + lock_, _ := b.locks.LoadOrStore(filename, &sync.RWMutex{}) + lock := lock_.(*sync.RWMutex) + lock.RLock() +} + +func (b *filesystemBackend) RUnlock(filename string) { + lock_, ok := b.locks.Load(filename) + if !ok { + panic("attempt to unlock non-existing lock") + } + lock := lock_.(*sync.RWMutex) + lock.RUnlock() +} + +func (b *filesystemBackend) Lock(filename string) { + lock_, _ := b.locks.LoadOrStore(filename, &sync.RWMutex{}) + lock := lock_.(*sync.RWMutex) + lock.Lock() +} + +func (b *filesystemBackend) Unlock(filename string) { + lock_, ok := b.locks.Load(filename) + if !ok { + panic("attempt to unlock non-existing lock") + } + lock := lock_.(*sync.RWMutex) + lock.Unlock() +} + func ensureLocalDir(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { err = os.MkdirAll(path, 0755) diff --git a/storage/filesystem_caldav.go b/storage/filesystem_caldav.go index 1162fd0..a495ba5 100644 --- a/storage/filesystem_caldav.go +++ b/storage/filesystem_caldav.go @@ -85,6 +85,9 @@ func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath return nil } + b.RLock(filename) + defer b.RUnlock(filename) + cal, err := calendarFromFile(filename, propFilter) if err != nil { fmt.Printf("load calendar error for %s: %v\n", filename, err) @@ -138,7 +141,12 @@ func (b *filesystemBackend) createDefaultCalendar(ctx context.Context) (*caldav. if err != nil { return nil, fmt.Errorf("error creating default calendar: %s", err.Error()) } - err = os.WriteFile(path.Join(localPath, calendarFileName), blob, 0644) + + filename := path.Join(localPath, calendarFileName) + b.Lock(filename) + defer b.Unlock(filename) + + err = os.WriteFile(filename, blob, 0644) if err != nil { return nil, fmt.Errorf("error writing default calendar: %s", err.Error()) } @@ -167,6 +175,10 @@ func (b *filesystemBackend) ListCalendars(ctx context.Context) ([]caldav.Calenda } calPath := path.Join(filename, calendarFileName) + + b.RLock(calPath) + defer b.RUnlock(calPath) + data, err := os.ReadFile(calPath) if err != nil { if os.IsNotExist(err) { @@ -209,6 +221,9 @@ func (b *filesystemBackend) GetCalendar(ctx context.Context, urlPath string) (*c log.Debug().Str("path", localPath).Msg("loading calendar") + b.RLock(localPath) + defer b.RUnlock(localPath) + data, err := os.ReadFile(localPath) if err != nil { if os.IsNotExist(err) { @@ -237,6 +252,9 @@ func (b *filesystemBackend) GetCalendarObject(ctx context.Context, objPath strin return nil, err } + b.RLock(localPath) + defer b.RUnlock(localPath) + info, err := os.Stat(localPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { @@ -320,6 +338,9 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin return nil, err } + b.Lock(localPath) + defer b.Unlock(localPath) + flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC // TODO handle IfNoneMatch == ETag if opts.IfNoneMatch.IsWildcard() { @@ -384,6 +405,10 @@ func (b *filesystemBackend) DeleteCalendarObject(ctx context.Context, path strin if err != nil { return err } + + b.Lock(localPath) + defer b.Unlock(localPath) + err = os.Remove(localPath) if err != nil { return err diff --git a/storage/filesystem_carddav.go b/storage/filesystem_carddav.go index 764dc10..01f0c89 100644 --- a/storage/filesystem_carddav.go +++ b/storage/filesystem_carddav.go @@ -69,6 +69,7 @@ func vcardPropFilter(card vcard.Card, props []string) vcard.Card { } func vcardFromFile(path string, propFilter []string) (vcard.Card, error) { + f, err := os.Open(path) if err != nil { return nil, err @@ -103,6 +104,10 @@ func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath s return nil } + // TODO use single file read for data & etag + b.RLock(filename) + defer b.RUnlock(filename) + card, err := vcardFromFile(filename, propFilter) if err != nil { return err @@ -141,7 +146,12 @@ func (b *filesystemBackend) writeAddressBook(ctx context.Context, ab *carddav.Ad if err != nil { return err } - return os.WriteFile(path.Join(localPath, addressBookFileName), blob, 0644) + + filename := path.Join(localPath, addressBookFileName) + b.Lock(filename) + defer b.Unlock(filename) + + err = os.WriteFile(filename, blob, 0644) if err != nil { return fmt.Errorf("error writing address book: %s", err.Error()) } @@ -208,6 +218,9 @@ func (b *filesystemBackend) ListAddressBooks(ctx context.Context) ([]carddav.Add } abPath := path.Join(filename, addressBookFileName) + b.RLock(abPath) + defer b.RUnlock(abPath) + data, err := os.ReadFile(abPath) if err != nil { if os.IsNotExist(err) { @@ -250,6 +263,9 @@ func (b *filesystemBackend) GetAddressBook(ctx context.Context, urlPath string) log.Debug().Str("path", localPath).Msg("loading addressbook") + b.RLock(localPath) + defer b.RUnlock(localPath) + data, err := os.ReadFile(localPath) if err != nil { if os.IsNotExist(err) { @@ -303,6 +319,9 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string return nil, err } + b.RLock(localPath) + defer b.RUnlock(localPath) + info, err := os.Stat(localPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { @@ -381,6 +400,9 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string return nil, err } + b.Lock(localPath) + defer b.Unlock(localPath) + flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC // TODO handle IfNoneMatch == ETag if opts.IfNoneMatch.IsWildcard() { @@ -444,6 +466,10 @@ func (b *filesystemBackend) DeleteAddressObject(ctx context.Context, path string if err != nil { return err } + + b.Lock(localPath) + defer b.Unlock(localPath) + err = os.Remove(localPath) if err != nil { return err From 7e03188c5a5cbbded11f88b021d4e606ba7d248f Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 7 Nov 2024 12:20:11 +0100 Subject: [PATCH 2/5] cmd/tokidoki: fix unused error --- cmd/tokidoki/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/tokidoki/main.go b/cmd/tokidoki/main.go index 9d706fd..87fd1b5 100644 --- a/cmd/tokidoki/main.go +++ b/cmd/tokidoki/main.go @@ -207,7 +207,10 @@ func main() { for sig := range sigCh { switch sig { case syscall.SIGINT, syscall.SIGTERM: - server.Shutdown(context.Background()) + err := server.Shutdown(context.Background()) + if err != nil { + log.Fatal().Err(err).Msg("Shutdown() error") + } return } } From fe0a0d0d00c4941a0d92a8639c2f9a4e95b3af65 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 7 Nov 2024 12:24:18 +0100 Subject: [PATCH 3/5] Add simple Makefile --- Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..941edb3 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ + +build: + go build ./cmd/tokidoki + +debug: + go build -tags "pam nullauth" ./cmd/tokidoki + +fmt: + go fmt ./... + +lint: + golangci-lint run From 665b206709787007be33d2da091a2a668739eb9c Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 7 Nov 2024 17:50:09 +0100 Subject: [PATCH 4/5] storage: streamline ETag calculation This commit introduces some helpers so that ETags can be calculated at the same time that files get read or written. Besides looking nicer, it should also help reduce lock contention around file access, as files do not need to be opened twice anymore. --- storage/etag.go | 58 +++++++++++++++++++++++++++++++++++ storage/filesystem.go | 9 ++---- storage/filesystem_caldav.go | 37 +++++++++------------- storage/filesystem_carddav.go | 39 +++++++++-------------- 4 files changed, 89 insertions(+), 54 deletions(-) create mode 100644 storage/etag.go diff --git a/storage/etag.go b/storage/etag.go new file mode 100644 index 0000000..1d22a5e --- /dev/null +++ b/storage/etag.go @@ -0,0 +1,58 @@ +package storage + +import ( + "crypto/sha1" + "encoding/base64" + "hash" + "io" +) + +type ETagWriter struct { + io.Writer + output io.Writer + csum hash.Hash +} + +// NewETagWriter creates a new io.Writer that pipes writes to the provided +// output while also calculating the contents ETag. +func NewETagWriter(output io.Writer) *ETagWriter { + csum := sha1.New() + return &ETagWriter{ + output: io.MultiWriter(output, csum), + csum: csum, + } +} + +func (e *ETagWriter) Write(p []byte) (n int, err error) { + return e.output.Write(p) +} + +func (e *ETagWriter) ETag() string { + csum := e.csum.Sum(nil) + return base64.StdEncoding.EncodeToString(csum[:]) +} + +type ETagReader struct { + io.Reader + input io.Reader + csum hash.Hash +} + +// NewETagReader creates a new io.Reader that pipes reads from the provided +// input while also calculating the contents ETag. +func NewETagReader(input io.Reader) *ETagReader { + csum := sha1.New() + return &ETagReader{ + input: io.TeeReader(input, csum), + csum: csum, + } +} + +func (e *ETagReader) Read(p []byte) (n int, err error) { + return e.input.Read(p) +} + +func (e *ETagReader) ETag() string { + csum := e.csum.Sum(nil) + return base64.StdEncoding.EncodeToString(csum[:]) +} diff --git a/storage/filesystem.go b/storage/filesystem.go index c6f3887..301c8f7 100644 --- a/storage/filesystem.go +++ b/storage/filesystem.go @@ -1,8 +1,6 @@ package storage import ( - "crypto/sha1" - "encoding/base64" "fmt" "io" "os" @@ -144,11 +142,10 @@ func etagForFile(path string) (string, error) { } defer f.Close() - h := sha1.New() - if _, err := io.Copy(h, f); err != nil { + src := NewETagReader(f) + if _, err := io.ReadAll(src); err != nil { return "", err } - csum := h.Sum(nil) - return base64.StdEncoding.EncodeToString(csum[:]), nil + return src.ETag(), nil } diff --git a/storage/filesystem_caldav.go b/storage/filesystem_caldav.go index a495ba5..fdab921 100644 --- a/storage/filesystem_caldav.go +++ b/storage/filesystem_caldav.go @@ -47,22 +47,23 @@ func (b *filesystemBackend) safeLocalCalDAVPath(ctx context.Context, urlPath str return b.safeLocalPath(homeSetPath, urlPath) } -func calendarFromFile(path string, propFilter []string) (*ical.Calendar, error) { +func calendarAndEtagFromFile(path string, propFilter []string) (*ical.Calendar, string, error) { f, err := os.Open(path) if err != nil { - return nil, err + return nil, "", err } defer f.Close() - dec := ical.NewDecoder(f) + src := NewETagReader(f) + dec := ical.NewDecoder(src) cal, err := dec.Decode() if err != nil { - return nil, err + return nil, "", err } - return cal, nil // TODO implement //return icalPropFilter(cal, propFilter), nil + return cal, src.ETag(), nil } func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath string, propFilter []string) ([]caldav.CalendarObject, error) { @@ -88,17 +89,12 @@ func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath b.RLock(filename) defer b.RUnlock(filename) - cal, err := calendarFromFile(filename, propFilter) + cal, etag, err := calendarAndEtagFromFile(filename, propFilter) if err != nil { fmt.Printf("load calendar error for %s: %v\n", filename, err) return err } - etag, err := etagForFile(filename) - if err != nil { - return err - } - // TODO can this potentially be called on a calendar object resource? // Would work (as Walk() includes root), except for the path construction below obj := caldav.CalendarObject{ @@ -108,6 +104,7 @@ func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath ETag: etag, Data: cal, } + log.Debug().Str("path", obj.Path).Str("etag", etag).Int64("size", info.Size()).Msg("calendar object loaded") result = append(result, obj) return nil }) @@ -268,17 +265,12 @@ func (b *filesystemBackend) GetCalendarObject(ctx context.Context, objPath strin propFilter = req.Props } - calendar, err := calendarFromFile(localPath, propFilter) + calendar, etag, err := calendarAndEtagFromFile(localPath, propFilter) if err != nil { log.Debug().Str("path", localPath).Err(err).Msg("error reading calendar") return nil, err } - etag, err := etagForFile(localPath) - if err != nil { - return nil, err - } - obj := caldav.CalendarObject{ Path: objPath, ModTime: info.ModTime(), @@ -286,6 +278,7 @@ func (b *filesystemBackend) GetCalendarObject(ctx context.Context, objPath strin ETag: etag, Data: calendar, } + log.Debug().Str("path", objPath).Str("etag", etag).Int64("size", info.Size()).Msg("returning calendar object") return &obj, nil } @@ -373,16 +366,13 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin } defer f.Close() - enc := ical.NewEncoder(f) + out := NewETagWriter(f) + enc := ical.NewEncoder(out) err = enc.Encode(calendar) if err != nil { return nil, err } - etag, err := etagForFile(localPath) - if err != nil { - return nil, err - } info, err := f.Stat() if err != nil { return nil, err @@ -392,9 +382,10 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin Path: objPath, ModTime: info.ModTime(), ContentLength: info.Size(), - ETag: etag, + ETag: out.ETag(), Data: calendar, } + log.Debug().Str("path", r.Path).Str("etag", r.ETag).Int64("size", info.Size()).Msg("calendar object updated") return &r, nil } diff --git a/storage/filesystem_carddav.go b/storage/filesystem_carddav.go index 01f0c89..6cdc49c 100644 --- a/storage/filesystem_carddav.go +++ b/storage/filesystem_carddav.go @@ -68,21 +68,21 @@ func vcardPropFilter(card vcard.Card, props []string) vcard.Card { return result } -func vcardFromFile(path string, propFilter []string) (vcard.Card, error) { - +func vcardAndEtagFromFile(path string, propFilter []string) (vcard.Card, string, error) { f, err := os.Open(path) if err != nil { - return nil, err + return nil, "", err } defer f.Close() - dec := vcard.NewDecoder(f) + src := NewETagReader(f) + dec := vcard.NewDecoder(src) card, err := dec.Decode() if err != nil { - return nil, err + return nil, "", err } - return vcardPropFilter(card, propFilter), nil + return vcardPropFilter(card, propFilter), src.ETag(), nil } func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath string, propFilter []string) ([]carddav.AddressObject, error) { @@ -104,16 +104,10 @@ func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath s return nil } - // TODO use single file read for data & etag b.RLock(filename) defer b.RUnlock(filename) - card, err := vcardFromFile(filename, propFilter) - if err != nil { - return err - } - - etag, err := etagForFile(filename) + card, etag, err := vcardAndEtagFromFile(filename, propFilter) if err != nil { return err } @@ -127,6 +121,7 @@ func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath s ETag: etag, Card: card, } + log.Debug().Str("path", obj.Path).Str("etag", etag).Int64("size", info.Size()).Msg("address object loaded") result = append(result, obj) return nil }) @@ -335,17 +330,12 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string propFilter = req.Props } - card, err := vcardFromFile(localPath, propFilter) + card, etag, err := vcardAndEtagFromFile(localPath, propFilter) if err != nil { log.Debug().Str("path", localPath).Err(err).Msg("error reading calendar") return nil, err } - etag, err := etagForFile(localPath) - if err != nil { - return nil, err - } - obj := carddav.AddressObject{ Path: objPath, ModTime: info.ModTime(), @@ -353,6 +343,7 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string ETag: etag, Card: card, } + log.Debug().Str("path", objPath).Str("etag", etag).Int64("size", info.Size()).Msg("returning address object") return &obj, nil } @@ -435,15 +426,12 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string } defer f.Close() - enc := vcard.NewEncoder(f) + out := NewETagWriter(f) + enc := vcard.NewEncoder(out) if err := enc.Encode(card); err != nil { return nil, err } - etag, err := etagForFile(localPath) - if err != nil { - return nil, err - } info, err := f.Stat() if err != nil { return nil, err @@ -453,9 +441,10 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string Path: objPath, ModTime: info.ModTime(), ContentLength: info.Size(), - ETag: etag, + ETag: out.ETag(), Card: card, } + log.Debug().Str("path", r.Path).Str("etag", r.ETag).Int64("size", info.Size()).Msg("address object updated") return &r, nil } From c6fdff75d039490f4ec6aed2d7a6c0ef1a2b1ab6 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 7 Nov 2024 17:53:29 +0100 Subject: [PATCH 5/5] Update dependencies --- go.mod | 18 +++++++++--------- go.sum | 37 +++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 0c7d558..08c3eb5 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module git.sr.ht/~sircmpwn/tokidoki go 1.18 require ( - git.sr.ht/~emersion/go-oauth2 v0.0.0-20240217160856-2e0d6e20b088 + git.sr.ht/~emersion/go-oauth2 v0.0.0-20240226120011-78f10ffd1d51 github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6 - github.com/emersion/go-imap/v2 v2.0.0-beta.2.0.20240417100641-a587a14d3f01 - github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9 - github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2 - github.com/go-chi/chi/v5 v5.0.12 + github.com/emersion/go-imap/v2 v2.0.0-beta.4 + github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff + github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135 + github.com/go-chi/chi/v5 v5.1.0 github.com/msteinert/pam/v2 v2.0.0 - github.com/rs/zerolog v1.32.0 - golang.org/x/crypto v0.18.0 + github.com/rs/zerolog v1.33.0 + golang.org/x/crypto v0.28.0 ) require ( github.com/emersion/go-message v0.18.1 // indirect - github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect + github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/teambition/rrule-go v1.8.2 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index f2a698e..d9d31a4 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,21 @@ -git.sr.ht/~emersion/go-oauth2 v0.0.0-20240217160856-2e0d6e20b088 h1:KuPliLD8CQM1WbCHdjHR6mhadIzLaAJCNENmvB1y9gs= -git.sr.ht/~emersion/go-oauth2 v0.0.0-20240217160856-2e0d6e20b088/go.mod h1:VHj0jSCLIkrfEwmOvJ4+ykpoVbD/YLN7BM523oKKBHc= +git.sr.ht/~emersion/go-oauth2 v0.0.0-20240226120011-78f10ffd1d51 h1:iz8Tm7obSouGC0atCd+NtFSmCgfxDizXD1Rm+0Jw75w= +git.sr.ht/~emersion/go-oauth2 v0.0.0-20240226120011-78f10ffd1d51/go.mod h1:VHj0jSCLIkrfEwmOvJ4+ykpoVbD/YLN7BM523oKKBHc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6 h1:kHoSgklT8weIDl6R6xFpBJ5IioRdBU1v2X2aCZRVCcM= github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6/go.mod h1:BEksegNspIkjCQfmzWgsgbu6KdeJ/4LwUZs7DMBzjzw= -github.com/emersion/go-imap/v2 v2.0.0-beta.2.0.20240417100641-a587a14d3f01 h1:dq/06hDbCT+/DpbKWSrfrTeiJW97ION78N6J6Mktp2w= -github.com/emersion/go-imap/v2 v2.0.0-beta.2.0.20240417100641-a587a14d3f01/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk= +github.com/emersion/go-imap/v2 v2.0.0-beta.4 h1:BS7+kUVhe/jfuFWgn8li0AbCKBIDoNvqJWsRJppltcc= +github.com/emersion/go-imap/v2 v2.0.0-beta.4/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk= github.com/emersion/go-message v0.18.1 h1:tfTxIoXFSFRwWaZsgnqS1DSZuGpYGzSmCZD8SK3QA2E= github.com/emersion/go-message v0.18.1/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= -github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY= -github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= -github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9 h1:ATgqloALX6cHCranzkLb8/zjivwQ9DWWDCQRnxTPfaA= +github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= +github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= -github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2 h1:k/NO/RfeXFuKGcpHDkspYoE8u6tWoHs03tH5DXg22To= -github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg= +github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= +github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135 h1:Ssk00uh7jhctJ23eclGxhhGqplSQB+wCt6fmbjhnOS8= +github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -26,15 +27,15 @@ github.com/msteinert/pam/v2 v2.0.0 h1:jnObb8MT6jvMbmrUQO5J/puTUjxy7Av+55zVJRJsCy github.com/msteinert/pam/v2 v2.0.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= -github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8= github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -53,12 +54,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=