Compare commits

..

5 Commits

29 changed files with 91 additions and 33 deletions

View File

@ -41,7 +41,12 @@ A config file named `calanonsync.json` is opened from the working directory. It
"Password": "" "Password": ""
}, },
"Anonymize": { "Anonymize": {
"Title": "#Work" "Title": {
"ReplaceWith": "#Work",
"Whitelist": [
"Something"
]
}
} }
} }
``` ```
@ -49,3 +54,22 @@ A config file named `calanonsync.json` is opened from the working directory. It
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.

View File

@ -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")

View File

@ -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

View File

@ -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"

View File

@ -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}

View File

@ -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