forked from aksdb/CalAnonSync
Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Andreas Schneider | 73a7754742 | |
Andreas Schneider | 8d8cc94213 | |
Andreas Schneider | 4d93b99cad | |
Andreas Schneider | 10b96ec705 | |
Andreas Schneider | cf04126d27 |
28
README.md
28
README.md
|
@ -41,11 +41,35 @@ A config file named `calanonsync.json` is opened from the working directory. It
|
||||||
"Password": ""
|
"Password": ""
|
||||||
},
|
},
|
||||||
"Anonymize": {
|
"Anonymize": {
|
||||||
"Title": "#Work"
|
"Title": {
|
||||||
|
"ReplaceWith": "#Work",
|
||||||
|
"Whitelist": [
|
||||||
|
"Something"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Both passwords are optional. If they are left blank, CalAnonSync will prompt for the password upon startup. (Recommended for security reasons!)
|
Both passwords are optional. If they are left blank, CalAnonSync will prompt for the password upon startup. (Recommended for security reasons!)
|
||||||
|
|
||||||
The CalDAV URL should point to the URL of a dedicated calendar. Beware that CalAnonSync will remove **all** events from that calendar that are not known to Exchange.
|
The CalDAV URL should point to the URL of a dedicated calendar. Beware that CalAnonSync will remove **all** events from that calendar that are not known to Exchange.
|
||||||
|
|
||||||
|
### Whitelist
|
||||||
|
|
||||||
|
If words (or phrases) are whitelisted, matches within the title for these words (or phrases) will be used as the
|
||||||
|
new title instead of the replacement. The order of these matches within the original title is kept, all non matching
|
||||||
|
parts of the title are simply stripped.
|
||||||
|
|
||||||
|
### Encryption
|
||||||
|
|
||||||
|
If you want to automate the sync process your probably have not much of a choice but storing the passwords
|
||||||
|
in the config file. Since plaintext passwords are always a big risk, CalAnonSync at least provides a simple
|
||||||
|
layer of eavesdropping security.
|
||||||
|
|
||||||
|
Using `calanonsync settings encrypt` you can encrypt all passwords in the config file. With `calanonsync settings decrypt`
|
||||||
|
you can revert that process.
|
||||||
|
|
||||||
|
Beware, that the encryption key is simply stored in a file alongside the config so it is really easy to decrypt.
|
||||||
|
It doesn't provide any security against a real attack and is only meant to prevent someone from getting access
|
||||||
|
to the password by looking over your shoulder.
|
||||||
|
|
22
build.go
22
build.go
|
@ -3,23 +3,37 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var reproducible = flag.Bool("r", false, "If set, the build will remove local directories from the debug infos.")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
env = append(env, "GOPATH="+wd)
|
|
||||||
|
|
||||||
args := []string{"build"}
|
env = append(env, "GOPATH="+wd, "CGO_ENABLED=0")
|
||||||
|
|
||||||
if len(os.Args) == 2 {
|
args := []string{"build", "-ldflags=-s -w"}
|
||||||
target := os.Args[1]
|
|
||||||
|
if *reproducible {
|
||||||
|
args = append(args,
|
||||||
|
"-asmflags=all=-trimpath="+wd,
|
||||||
|
"-gcflags=all=-trimpath="+wd,
|
||||||
|
"-a",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(flag.Args()) == 1 {
|
||||||
|
target := flag.Arg(0)
|
||||||
targetParts := strings.Split(target, "/")
|
targetParts := strings.Split(target, "/")
|
||||||
if len(targetParts) != 2 {
|
if len(targetParts) != 2 {
|
||||||
println("Invalid target specification. Example: windows/386")
|
println("Invalid target specification. Example: windows/386")
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ThomsonReutersEikon/go-ntlm"
|
|
||||||
packages = [
|
|
||||||
"ntlm",
|
|
||||||
"ntlm/md4"
|
|
||||||
]
|
|
||||||
revision = "2a7c173f9e18233a4ae29891da6a0a63637e2d8d"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
@ -28,12 +19,6 @@
|
||||||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/vadimi/go-http-ntlm"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "bc5a8d8d91a12dd386d3fa1019abb8bb681bdd41"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
|
@ -52,6 +37,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "8a1421bb063ff7faff2c4276042f954d7965c60c6a51afc95b658ac721cfcea0"
|
inputs-digest = "d2f5b5a67e95e173cd0d93a24576cdc1e5384e2bc0246f8cbed88838514b8ec0"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
# go-tests = true
|
# go-tests = true
|
||||||
# unused-packages = true
|
# unused-packages = true
|
||||||
|
|
||||||
|
ignored = ["github.com/vadimi/go-http-ntlm"]
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
|
@ -33,14 +34,6 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/vadimi/go-http-ntlm"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Azure/go-ntlmssp"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/spf13/cobra"
|
name = "github.com/spf13/cobra"
|
||||||
version = "0.0.2"
|
version = "0.0.2"
|
||||||
|
|
|
@ -8,12 +8,17 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var syncSettings = struct {
|
||||||
|
rebuild bool
|
||||||
|
}{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: "calanonsync",
|
Use: "calanonsync",
|
||||||
Short: "Synchronize a calendar from EWS to CalDAV by event time and an anonymized title only.",
|
Short: "Synchronize a calendar from EWS to CalDAV by event time and an anonymized title only.",
|
||||||
Run: runSynchronization,
|
Run: runSynchronization,
|
||||||
}
|
}
|
||||||
|
rootCmd.Flags().BoolVar(&syncSettings.rebuild, "rebuild", false, "Rebuild all calendar items, no matter if they already exist.")
|
||||||
|
|
||||||
rootCmd.AddCommand(InitSettingsCmd())
|
rootCmd.AddCommand(InitSettingsCmd())
|
||||||
|
|
||||||
|
@ -66,6 +71,13 @@ func runSynchronization(cmd *cobra.Command, args []string) {
|
||||||
for uid, calDavItem := range calDavItemMap {
|
for uid, calDavItem := range calDavItemMap {
|
||||||
if ewsItem, ok := relevantEWSItems[uid]; ok {
|
if ewsItem, ok := relevantEWSItems[uid]; ok {
|
||||||
// Good, so we still know the item at least.
|
// Good, so we still know the item at least.
|
||||||
|
|
||||||
|
// If we want a full rebuild, we can skip this step
|
||||||
|
// since we will create new items anyway.
|
||||||
|
if syncSettings.rebuild {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !ewsItem.Start.Equal(calDavItem.Start()) ||
|
if !ewsItem.Start.Equal(calDavItem.Start()) ||
|
||||||
!ewsItem.End.Equal(calDavItem.End()) {
|
!ewsItem.End.Equal(calDavItem.End()) {
|
||||||
|
|
||||||
|
@ -92,9 +104,9 @@ func runSynchronization(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find items we don't know so far and create them.
|
// Find items we don't know so far and create them. Also recreate them if we want to rebuild all.
|
||||||
for uid, ewsItem := range relevantEWSItems {
|
for uid, ewsItem := range relevantEWSItems {
|
||||||
if _, ok := calDavItemMap[uid]; !ok {
|
if _, ok := calDavItemMap[uid]; !ok || syncSettings.rebuild {
|
||||||
title := s.Anonymize.Title.Apply(ewsItem.Subject)
|
title := s.Anonymize.Title.Apply(ewsItem.Subject)
|
||||||
ical := CreateICal(ewsItem.Hash(), title, ewsItem.Start, ewsItem.End, ewsItem.IsAllDayEvent)
|
ical := CreateICal(ewsItem.Hash(), title, ewsItem.Start, ewsItem.End, ewsItem.IsAllDayEvent)
|
||||||
calDavItem := CalDAVItem{HRef: uid + ".ics", ICal: ical}
|
calDavItem := CalDAVItem{HRef: uid + ".ics", ICal: ical}
|
||||||
|
|
|
@ -131,7 +131,13 @@ over-the-shoulder "attacks".`,
|
||||||
Run: runSettingsDecryption,
|
Run: runSettingsDecryption,
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsCmd.AddCommand(encryptCmd, decryptCmd)
|
initCmd := &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "Initialize an empty but valid settings file.",
|
||||||
|
Run: runSettingsInit,
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsCmd.AddCommand(encryptCmd, decryptCmd, initCmd)
|
||||||
return settingsCmd
|
return settingsCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +231,30 @@ func runSettingsDecryption(cmd *cobra.Command, args []string) {
|
||||||
log.Println("Settings decrypted")
|
log.Println("Settings decrypted")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runSettingsInit(cmd *cobra.Command, args []string) {
|
||||||
|
if _, err := os.Stat(settingsName); err == nil || os.IsExist(err) {
|
||||||
|
log.Fatalln("You already have a settings file! Remove that first if you really want a new blank one!")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := Settings{}
|
||||||
|
s.Anonymize.Title = &StringAnonSettings{ReplaceWith: "Replacement", Whitelist: []string{}}
|
||||||
|
|
||||||
|
// Rewrite the settings file.
|
||||||
|
f, err := os.OpenFile(settingsName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not rewrite settings: %s\n", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
e := json.NewEncoder(f)
|
||||||
|
e.SetIndent("", " ")
|
||||||
|
err = e.Encode(&s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Blank settings have been created.")
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the anonymization rule to the given string, returning the
|
// Apply the anonymization rule to the given string, returning the
|
||||||
// anonymized version.
|
// anonymized version.
|
||||||
// If the anonymization is nil or empty, the original string will
|
// If the anonymization is nil or empty, the original string will
|
||||||
|
|
Loading…
Reference in New Issue