├── user.csv ├── .gitignore ├── .github └── workflows │ └── go.yml ├── README.md ├── user.go ├── goCal.go └── handler.go /user.csv: -------------------------------------------------------------------------------- 1 | admin,123456,rw 2 | foo,bar,rw 3 | lorem,ipsum,ro 4 | ,,ro -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ics 2 | *.zip 3 | ssl.cert 4 | ssl.key 5 | goCal 6 | ssl.crt 7 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.13 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.13 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | if [ -f Gopkg.toml ]; then 23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 24 | dep ensure 25 | fi 26 | 27 | - name: Build 28 | run: go build -v . 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goCal 2 | 3 | an experimental CalDav Server in Golang 4 | based on 5 | 6 | ## install 7 | 8 | install goCal with go get: 9 | 10 | ```bash 11 | go get -u github.com/SimonWaldherr/goCal 12 | ``` 13 | 14 | ## start 15 | 16 | Before using goCal you need to check its configuration. 17 | You can manage all user-accounts in the ```user.csv```-file. 18 | To start goCal run this in your Terminal: 19 | 20 | ```bash 21 | goCal -port=80 -sport=443 -sslcrt="path/to/your/sslcert.crt" -sslkey="path/to/your/sslkey.key" -user="path/to/your/user.csv" -storage="path/to/the/folder/where/the/ics-files/will/be/stored" 22 | ``` 23 | 24 | ## use 25 | 26 | now you can start using goCal, connect to your CalDAV-Server via https://localhost:443/ or subscribe to your ics-feed at https://localhost:443/icsfeed/ (or via webcal://localhost/). 27 | 28 | if you use a wordpress blog, you can install ```ICS Calendar``` and show your events at your blog. 29 | -------------------------------------------------------------------------------- /user.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/csv" 6 | "io" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | type accessType string 12 | 13 | const ( 14 | readonly accessType = "RO" 15 | writeonly accessType = "WO" 16 | readwrite accessType = "RW" 17 | ) 18 | 19 | type userData struct { 20 | name string 21 | password string 22 | access accessType 23 | } 24 | 25 | func loadUserDataFromFile(filename string) map[string]userData { 26 | fp, _ := os.Open(filename) 27 | return loadCSV(bufio.NewReader(fp)) 28 | } 29 | 30 | func loadCSV(reader io.Reader) map[string]userData { 31 | var data = map[string]userData{} 32 | var accT accessType 33 | 34 | csvReader := csv.NewReader(reader) 35 | csvReader.Comma = ',' 36 | for { 37 | record, err := csvReader.Read() 38 | if err == io.EOF { 39 | break 40 | } 41 | if len(record) > 1 { 42 | if len(record) > 2 { 43 | switch strings.ToUpper(record[2]) { 44 | case "RO": 45 | accT = readonly 46 | case "WO": 47 | accT = writeonly 48 | case "RW": 49 | accT = readwrite 50 | default: 51 | accT = readonly 52 | } 53 | } else { 54 | accT = readonly 55 | } 56 | data[record[0]] = userData{ 57 | name: record[0], 58 | password: record[1], 59 | access: accT, 60 | } 61 | } 62 | } 63 | return data 64 | } 65 | -------------------------------------------------------------------------------- /goCal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | 7 | "simonwaldherr.de/go/caldav-go/files" 8 | "simonwaldherr.de/go/golibs/file" 9 | "simonwaldherr.de/go/gwv" 10 | ) 11 | 12 | var ( 13 | SSLCert string 14 | SSLKey string 15 | UserFile string 16 | StoragePath string 17 | HTTPPort int 18 | HTTPSPort int 19 | ) 20 | 21 | var userdata map[string]userData 22 | 23 | func init() { 24 | flag.StringVar(&SSLCert, "sslcrt", "ssl.crt", "path to the ssl/tls crt file") 25 | flag.StringVar(&SSLKey, "sslkey", "ssl.key", "path to the ssl/tls key file") 26 | flag.StringVar(&UserFile, "user", "user.csv", "path to the user.csv file") 27 | flag.StringVar(&StoragePath, "storage", "icsdata", "path to the folder with ics data") 28 | flag.IntVar(&HTTPPort, "port", 80, "http port to listen") 29 | flag.IntVar(&HTTPSPort, "sport", 443, "https port to listen") 30 | flag.Parse() 31 | 32 | userdata = loadUserDataFromFile(UserFile) 33 | files.StoragePath = StoragePath 34 | } 35 | 36 | func main() { 37 | if !file.Exists(SSLCert) || !file.Exists(SSLKey) { 38 | options := map[string]string{} 39 | options["certPath"] = SSLCert 40 | options["keyPath"] = SSLKey 41 | options["host"] = "*" 42 | options["countryName"] = "DE" 43 | options["provinceName"] = "Bavaria" 44 | options["organizationName"] = "Lorem Ipsum Ltd" 45 | options["commonName"] = "*" 46 | 47 | gwv.GenerateSSL(options) 48 | } 49 | 50 | startServer() 51 | } 52 | 53 | func caldavhandler(rw http.ResponseWriter, req *http.Request) (string, int) { 54 | CalDAVHandler(rw, req) 55 | return "", 0 56 | } 57 | 58 | func feedhandler(rw http.ResponseWriter, req *http.Request) (string, int) { 59 | FeedHandler(rw, req) 60 | return "", 0 61 | } 62 | 63 | func startServer() { 64 | HTTPD := gwv.NewWebServer(HTTPPort, 60) 65 | HTTPD.ConfigSSL(HTTPSPort, SSLKey, SSLCert, true) 66 | 67 | HTTPD.URLhandler( 68 | gwv.URL("^/icsfeed/", feedhandler, gwv.MANUAL), 69 | gwv.URL("^/", caldavhandler, gwv.MANUAL), 70 | ) 71 | 72 | HTTPD.Start() 73 | HTTPD.WG.Wait() 74 | } 75 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "simonwaldherr.de/go/caldav-go/handlers" 12 | "simonwaldherr.de/go/golibs/file" 13 | ) 14 | 15 | func checkMethod(access accessType, method string) bool { 16 | switch access { 17 | case readonly: 18 | if method == "GET" || method == "PROPFIND" || method == "OPTIONS" { 19 | return true 20 | } 21 | case writeonly: 22 | if method == "PUT" || method == "PROPFIND" || method == "OPTIONS" { 23 | return true 24 | } 25 | case readwrite: 26 | return true 27 | } 28 | return false 29 | } 30 | 31 | func CalDAVHandler(writer http.ResponseWriter, request *http.Request) { 32 | var udata userData 33 | var realm string = "Please enter your username and password for this site" 34 | 35 | addr := request.RemoteAddr 36 | if i := strings.LastIndex(addr, ":"); i != -1 { 37 | addr = addr[:i] 38 | } 39 | 40 | fmt.Printf("%s - - [%s] %q %d %d %q %q\n", 41 | addr, 42 | time.Now().Format("02/Jan/2006:15:04:05 -0700"), 43 | fmt.Sprintf("%s %s %s", request.Method, request.URL, request.Proto), 44 | 0, 45 | 0, 46 | request.Referer(), 47 | request.UserAgent()) 48 | 49 | user, pass, ok := request.BasicAuth() 50 | 51 | if ok { 52 | log.Printf("Check Authentication %v\n", user) 53 | udata, ok = userdata[user] 54 | } 55 | 56 | if (!ok || pass != udata.password) || !checkMethod(udata.access, request.Method) { 57 | log.Printf("Not authorized user: %v, access type: %v\n", user, udata.access) 58 | writer.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) 59 | writer.WriteHeader(401) 60 | writer.Write([]byte("Unauthorised.\n")) 61 | return 62 | } 63 | 64 | response := HandleCalDAVRequest(request) 65 | response.Write(writer) 66 | } 67 | 68 | func FeedHandler(writer http.ResponseWriter, request *http.Request) { 69 | var realm string = "Please enter your username and password for this site" 70 | 71 | addr := request.RemoteAddr 72 | if i := strings.LastIndex(addr, ":"); i != -1 { 73 | addr = addr[:i] 74 | } 75 | fmt.Printf("%s - - [%s] %q %d %d %q %q\n", 76 | addr, 77 | time.Now().Format("02/Jan/2006:15:04:05 -0700"), 78 | fmt.Sprintf("%s %s %s", request.Method, request.URL, request.Proto), 79 | 0, 80 | 0, 81 | request.Referer(), 82 | request.UserAgent()) 83 | 84 | if udata, ok := userdata[""]; !ok { 85 | user, pass, ok := request.BasicAuth() 86 | 87 | if ok { 88 | log.Printf("Check Authentication %v\n", user) 89 | udata, ok = userdata[user] 90 | } 91 | 92 | if (!ok || pass != udata.password) || !checkMethod(udata.access, request.Method) { 93 | log.Printf("Not authorized user: %v, access type: %v\n", user, udata.access) 94 | writer.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) 95 | writer.WriteHeader(401) 96 | writer.Write([]byte("Unauthorised.\n")) 97 | return 98 | } 99 | } 100 | 101 | writer.Write([]byte("BEGIN:VCALENDAR\n")) 102 | file.Each(StoragePath, true, func(filename, extension, filepath string, dir bool, fileinfo os.FileInfo) { 103 | if extension == "ics" && !dir { 104 | str, _ := file.Read(filepath) 105 | str = strings.Replace(str, "BEGIN:VCALENDAR", "", 1) 106 | str = strings.Replace(str, "END:VCALENDAR", "", 1) 107 | writer.Write([]byte(str)) 108 | } 109 | }) 110 | writer.Write([]byte("END:VCALENDAR\n")) 111 | } 112 | 113 | func HandleCalDAVRequest(request *http.Request) *handlers.Response { 114 | handler := handlers.NewHandler(request) 115 | return handler.Handle() 116 | } 117 | --------------------------------------------------------------------------------