init
This commit is contained in:
commit
a0a338067b
16 changed files with 898 additions and 0 deletions
8
Makefile
Normal file
8
Makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# /usr/share/webdav/templates
|
||||||
|
all:
|
||||||
|
go build -o webdav ./src
|
||||||
|
mkdir -p /srv/bin
|
||||||
|
mv webdav /srv/bin/
|
||||||
|
cp webdav.service /etc/systemd/system/
|
||||||
|
systemctl enable webdav
|
||||||
|
systemctl start webdav
|
7
go.mod
Normal file
7
go.mod
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module webdav
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.1
|
||||||
|
|
||||||
|
require golang.org/x/net v0.37.0
|
2
go.sum
Normal file
2
go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
20
misc/default
Normal file
20
misc/default
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://unix:/run/webdav.sock:/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_pass_request_headers on;
|
||||||
|
client_max_body_size 2G;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
15
misc/webdav.service
Normal file
15
misc/webdav.service
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[Unit]
|
||||||
|
Description = WebDAV Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User = root
|
||||||
|
Group = root
|
||||||
|
Type = simple
|
||||||
|
ExecStart = /srv/bin/webdav
|
||||||
|
#Restart = on-abort
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec = 5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy = multi-user.target
|
250
question.md
Normal file
250
question.md
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
|
||||||
|
I’m having a problem with the WebDAV implementation I demonstrated below. The server works, but when it creates files, the content is empty.
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
```go: main.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebDAV handler with authentication
|
||||||
|
func webdavHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="WebDAV"`)
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !PAMAuthenticate(username, password) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user's home directory
|
||||||
|
userInfo, err := user.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "User not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// webdavPath := userInfo.HomeDir + "/website"
|
||||||
|
webdavPath := userInfo.HomeDir
|
||||||
|
|
||||||
|
// Check if the directory exists
|
||||||
|
if _, err := os.Stat(webdavPath); os.IsNotExist(err) {
|
||||||
|
http.Error(w, "WebDAV directory not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
handler := &webdav.Handler{
|
||||||
|
Prefix: "/",
|
||||||
|
FileSystem: webdav.Dir(webdavPath),
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("new connection")
|
||||||
|
*/
|
||||||
|
|
||||||
|
uid, gid, err := GetUserData(username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "WebDAV directory not found", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := &webdav.Handler{
|
||||||
|
Prefix: "/",
|
||||||
|
FileSystem: WebDavDir{Dir: webdav.Dir(webdavPath), UID: uid, GID: gid},
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("New connection: %s\n", username)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("WebDAV server running on :8800")
|
||||||
|
http.HandleFunc("/", webdavHandler)
|
||||||
|
// This binds to 127.0.0.1
|
||||||
|
log.Fatal(http.ListenAndServe(":8800", nil))
|
||||||
|
//log.Fatal(http.ListenAndServe("0.0.0.0:8800", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```go: fs.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebDavFile struct {
|
||||||
|
webdav.File
|
||||||
|
|
||||||
|
FullPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebDavDir struct {
|
||||||
|
webdav.Dir
|
||||||
|
|
||||||
|
UID int
|
||||||
|
GID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserData(username string) (int, int, error) {
|
||||||
|
userInfo, err := user.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("User not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(userInfo.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gid, err := strconv.Atoi(userInfo.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs WebDavDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||||
|
err := fs.Dir.Mkdir(ctx, name, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(string(fs.Dir), name)
|
||||||
|
if chownErr := os.Chown(fullPath, fs.UID, fs.GID); chownErr != nil {
|
||||||
|
return chownErr
|
||||||
|
log.Printf("Mkdir chown error: %v", chownErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs WebDavDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||||
|
fullPath := filepath.Join(string(fs.Dir), name)
|
||||||
|
|
||||||
|
log.Printf("OpenFile: %s, flag=%d, perm=%o", fullPath, flag, perm)
|
||||||
|
|
||||||
|
f, err := fs.Dir.OpenFile(ctx, name, flag, perm)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("File opening error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("open file for: uid=%d, gid=%o", fs.UID, fs.GID)
|
||||||
|
|
||||||
|
if chownErr := os.Chown(fullPath, fs.UID, fs.GID); chownErr != nil {
|
||||||
|
log.Printf("File chown error: %v", chownErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebDavFile{
|
||||||
|
File: f,
|
||||||
|
FullPath: fullPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f WebDavFile) Write(p []byte) (int, error) {
|
||||||
|
log.Printf("Writing %d bytes to %s", len(p), f.FullPath)
|
||||||
|
return f.File.Write(p)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```c: pam.c
|
||||||
|
#define _POSIX_C_SOURCE 200809L // Ensure strdup() is available
|
||||||
|
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// PAM Conversation Function
|
||||||
|
int pam_conv_func(int num_msg, const struct pam_message **msg,
|
||||||
|
struct pam_response **resp, void *appdata_ptr) {
|
||||||
|
struct pam_response *responses = (struct pam_response *)malloc(num_msg * sizeof(struct pam_response));
|
||||||
|
if (!responses) return PAM_CONV_ERR;
|
||||||
|
|
||||||
|
char *password = (char *)appdata_ptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_msg; i++) {
|
||||||
|
responses[i].resp_retcode = 0;
|
||||||
|
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
|
||||||
|
responses[i].resp = strdup(password);
|
||||||
|
} else {
|
||||||
|
responses[i].resp = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*resp = responses;
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to authenticate user with PAM
|
||||||
|
int authenticate_pam(const char *username, const char *password) {
|
||||||
|
struct pam_conv conv = { pam_conv_func, (void *)password };
|
||||||
|
pam_handle_t *pamh = NULL;
|
||||||
|
|
||||||
|
int retval = pam_start("login", username, &conv, &pamh);
|
||||||
|
if (retval != PAM_SUCCESS) return retval;
|
||||||
|
|
||||||
|
retval = pam_authenticate(pamh, 0);
|
||||||
|
pam_end(pamh, retval);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```go: pam.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo CFLAGS: -std=c11
|
||||||
|
#cgo LDFLAGS: -lpam
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
|
||||||
|
extern int authenticate_pam(const char *username, const char *password);
|
||||||
|
|
||||||
|
// Define PAM_SUCCESS explicitly for cgo
|
||||||
|
static const int CGO_PAM_SUCCESS = PAM_SUCCESS;
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authenticate using PAM (directly linked C function)
|
||||||
|
func PAMAuthenticate(username, password string) bool {
|
||||||
|
cUsername := C.CString(username)
|
||||||
|
cPassword := C.CString(password)
|
||||||
|
defer C.free(unsafe.Pointer(cUsername))
|
||||||
|
defer C.free(unsafe.Pointer(cPassword))
|
||||||
|
|
||||||
|
result := C.authenticate_pam(cUsername, cPassword)
|
||||||
|
return result == C.PAM_SUCCESS
|
||||||
|
//return result == C.CGO_PAM_SUCCESS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
21
readme.md
Normal file
21
readme.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
useradd -mg users -s /usr/bin/zsh test
|
||||||
|
passwd test
|
||||||
|
|
||||||
|
# make it accessible for to the Nginx
|
||||||
|
chmod 755 /home/test
|
||||||
|
chmod 755 /home/test/website
|
||||||
|
```
|
||||||
|
|
||||||
|
curl -u youruser:yourpass -T test.txt http://localhost:8800/test.txt
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
GOTOOLCHAIN=auto go mod vendor
|
||||||
|
GOTOOLCHAIN=auto go run .
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
apt install libpam0g-dev
|
93
src/file-manager.go
Normal file
93
src/file-manager.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"html/template"
|
||||||
|
"os"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
var templates = template.Must(template.ParseGlob("templates/*.html"))
|
||||||
|
|
||||||
|
// curl -H "Accept: application/json" http://localhost:9988/
|
||||||
|
func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("got / request\n")
|
||||||
|
//io.WriteString(w, "This is my website!\n")
|
||||||
|
|
||||||
|
c := ListFiles("")
|
||||||
|
fmt.Println(c)
|
||||||
|
|
||||||
|
context := struct {
|
||||||
|
IsLoggedIn bool ; Files []struct {FileName string}}{
|
||||||
|
IsLoggedIn: false,
|
||||||
|
Files: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if request wants JSON
|
||||||
|
if r.Header.Get("Accept") == "application/json" {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//tmpl, _ := templates.ParseFiles("templates/venue.html")
|
||||||
|
|
||||||
|
templates.ExecuteTemplate(w, "index.html", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListFiles(dir string) []struct {FileName string} {
|
||||||
|
context := []struct {FileName string} {}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(".")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
info, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error getting info:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
context = append(context, struct {FileName string}{
|
||||||
|
FileName: info.Name(),
|
||||||
|
})
|
||||||
|
|
||||||
|
//fmt.Printf("Name: %s, Size: %d bytes, IsDir: %t\n", info.Name(), info.Size(), info.IsDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHello(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("got /hello request\n")
|
||||||
|
io.WriteString(w, "Hello, HTTP!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMenusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//fmt.Fprintf(w, "Hello, World!")
|
||||||
|
templates.ExecuteTemplate(w, "index.html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPmain() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
http.HandleFunc("/", getRoot)
|
||||||
|
http.HandleFunc("/hello", getHello)
|
||||||
|
|
||||||
|
err := http.ListenAndServe(":3333", nil)
|
||||||
|
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
fmt.Printf("server closed\n")
|
||||||
|
} else if err != nil {
|
||||||
|
fmt.Printf("error starting server: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
133
src/fs.go
Normal file
133
src/fs.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalLockSystem = webdav.NewMemLS()
|
||||||
|
|
||||||
|
type WebDavFile struct {
|
||||||
|
//webdav.File
|
||||||
|
|
||||||
|
FullPath string
|
||||||
|
File *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebDavDir struct {
|
||||||
|
webdav.Dir
|
||||||
|
|
||||||
|
UID int
|
||||||
|
GID int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func GetUserData(username string) (int, int, error) {
|
||||||
|
userInfo, err := user.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("User not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(userInfo.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gid, err := strconv.Atoi(userInfo.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs WebDavDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||||
|
err := fs.Dir.Mkdir(ctx, name, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(string(fs.Dir), name)
|
||||||
|
if chownErr := os.Chown(fullPath, fs.UID, fs.GID); chownErr != nil {
|
||||||
|
log.Printf("Mkdir chown error: %v", chownErr)
|
||||||
|
return chownErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs WebDavDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||||
|
fullPath := filepath.Join(string(fs.Dir), name)
|
||||||
|
log.Printf("OpenFile: %s, flag=%d, perm=%o", fullPath, flag, perm)
|
||||||
|
|
||||||
|
//f, err := os.OpenFile(fullPath, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0666)
|
||||||
|
f, err := os.OpenFile(fullPath, flag, perm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if chownErr := os.Chown(fullPath, fs.UID, fs.GID); chownErr != nil {
|
||||||
|
log.Printf("OpenFile chown error: %v", chownErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WebDavFile{
|
||||||
|
File: f,
|
||||||
|
FullPath: fullPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WebDavFile) Read(p []byte) (int, error) { return f.File.Read(p) }
|
||||||
|
func (f *WebDavFile) Write(p []byte) (int, error) {
|
||||||
|
log.Printf("Writing %d bytes to %s", len(p), f.FullPath)
|
||||||
|
return f.File.Write(p)
|
||||||
|
}
|
||||||
|
func (f *WebDavFile) Seek(offset int64, whence int) (int64, error) { return f.File.Seek(offset, whence) }
|
||||||
|
func (f *WebDavFile) Close() error { return f.File.Close() }
|
||||||
|
func (f *WebDavFile) Readdir(count int) ([]os.FileInfo, error) { return f.File.Readdir(count) }
|
||||||
|
func (f *WebDavFile) Stat() (os.FileInfo, error) { return f.File.Stat() }
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (fs WebDavDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||||
|
err := fs.Dir.Mkdir(ctx, name, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(string(fs.Dir), name)
|
||||||
|
if chownErr := os.Chown(fullPath, fs.UID, fs.GID); chownErr != nil {
|
||||||
|
return chownErr
|
||||||
|
log.Printf("Mkdir chown error: %v", chownErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs WebDavDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||||
|
fullPath := filepath.Join(string(fs.Dir), name)
|
||||||
|
|
||||||
|
log.Printf("OpenFile: %s, flag=%d, perm=%o", fullPath, flag, perm)
|
||||||
|
|
||||||
|
f, err := fs.Dir.OpenFile(ctx, name, flag, perm)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("File opening error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("open file for: uid=%d, gid=%o", fs.UID, fs.GID)
|
||||||
|
|
||||||
|
if chownErr := os.Chown(fullPath, fs.UID, fs.GID); chownErr != nil {
|
||||||
|
log.Printf("File chown error: %v", chownErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebDavFile{
|
||||||
|
File: f,
|
||||||
|
FullPath: fullPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f WebDavFile) Write(p []byte) (int, error) {
|
||||||
|
log.Printf("Writing %d bytes to %s", len(p), f.FullPath)
|
||||||
|
return f.File.Write(p)
|
||||||
|
}*/
|
159
src/main.go
Executable file
159
src/main.go
Executable file
|
@ -0,0 +1,159 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebDAV handler with authentication
|
||||||
|
func webdavHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
handler := &webdav.Handler{
|
||||||
|
FileSystem: webdav.Dir("."),
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="WebDAV"`)
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
if !PAMAuthenticate(username, password) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
fmt.Printf("PAM checking time: %dms\n", elapsed.Milliseconds())
|
||||||
|
|
||||||
|
// start = time.Now()
|
||||||
|
// isValid, _ := checkPassword("user", "pass")
|
||||||
|
// elapsed = time.Since(start)
|
||||||
|
// fmt.Printf("Direct checking time: %dms\n %d", elapsed.Milliseconds(), isValid)
|
||||||
|
|
||||||
|
// start = time.Now()
|
||||||
|
// isValid, _ = checkPassword("user", "pass1")
|
||||||
|
// elapsed = time.Since(start)
|
||||||
|
// fmt.Printf("Direct checking time: %dms\n %d", elapsed.Milliseconds(), isValid)
|
||||||
|
|
||||||
|
// Get the user's home directory
|
||||||
|
userInfo, err := user.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "User not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// webdavPath := userInfo.HomeDir + "/website"
|
||||||
|
webdavPath := userInfo.HomeDir
|
||||||
|
|
||||||
|
// Check if the directory exists
|
||||||
|
if _, err := os.Stat(webdavPath); os.IsNotExist(err) {
|
||||||
|
http.Error(w, "WebDAV directory not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
handler := &webdav.Handler{
|
||||||
|
Prefix: "/",
|
||||||
|
FileSystem: webdav.Dir(webdavPath),
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("new connection")
|
||||||
|
*/
|
||||||
|
|
||||||
|
uid, gid, err := GetUserData(username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "WebDAV directory not found", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
handler := &webdav.Handler{
|
||||||
|
Prefix: "/",
|
||||||
|
FileSystem: WebDavDir{Dir: webdav.Dir(webdavPath), UID: uid, GID: gid},
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
handler := &webdav.Handler{
|
||||||
|
Prefix: "/",
|
||||||
|
FileSystem: WebDavDir{Dir: webdav.Dir(webdavPath), UID: uid, GID: gid},
|
||||||
|
//LockSystem: webdav.NewMemLS(),
|
||||||
|
//LockSystem: nil, // disable locking
|
||||||
|
LockSystem: globalLockSystem,
|
||||||
|
Logger: func(r *http.Request, err error) {
|
||||||
|
log.Printf("%s %s, error: %v", r.Method, r.URL.Path, err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("New connection: %s\n", username)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this method might not work corractly with gnome or other clients
|
||||||
|
func isWebDAVRequest(r *http.Request) bool {
|
||||||
|
switch r.Method {
|
||||||
|
case "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finder sends GET/OPTIONS with Depth header when doing WebDAV
|
||||||
|
if r.Header.Get("Depth") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: check User-Agent or other headers if needed
|
||||||
|
ua := r.UserAgent()
|
||||||
|
if strings.Contains(ua, "WebDAV") || strings.Contains(ua, "Microsoft-WebDAV") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// http.HandleFunc("/", webdavHandler)
|
||||||
|
// http.HandleFunc("/", getRoot)
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if isWebDAVRequest(r) {
|
||||||
|
webdavHandler(w, r)
|
||||||
|
} else {
|
||||||
|
getRoot(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// This binds to 127.0.0.1 prevents for accessing the app from the outside
|
||||||
|
//log.Fatal(http.ListenAndServe(":8800", nil))
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
envValue := os.Getenv("WEBDAV_SERV_PORT")
|
||||||
|
if port, err := strconv.ParseInt(envValue, 10, 64); err == nil {
|
||||||
|
fmt.Printf("WebDAV server running on :%d\n", port)
|
||||||
|
log.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fmt.Println("WebDAV server running socket")
|
||||||
|
startUnixSocket()
|
||||||
|
}
|
||||||
|
}
|
44
src/pam.c
Executable file
44
src/pam.c
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
// it works with file ending file_linux.go (and c probably as well)
|
||||||
|
|
||||||
|
#define _POSIX_C_SOURCE 200809L // Ensure strdup() is available
|
||||||
|
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// PAM Conversation Function
|
||||||
|
int pam_conv_func(int num_msg, const struct pam_message **msg,
|
||||||
|
struct pam_response **resp, void *appdata_ptr) {
|
||||||
|
struct pam_response *responses = (struct pam_response *)malloc(num_msg * sizeof(struct pam_response));
|
||||||
|
if (!responses) return PAM_CONV_ERR;
|
||||||
|
|
||||||
|
char *password = (char *)appdata_ptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_msg; i++) {
|
||||||
|
responses[i].resp_retcode = 0;
|
||||||
|
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
|
||||||
|
responses[i].resp = strdup(password);
|
||||||
|
} else {
|
||||||
|
responses[i].resp = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*resp = responses;
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to authenticate user with PAM
|
||||||
|
int authenticate_pam(const char *username, const char *password) {
|
||||||
|
struct pam_conv conv = { pam_conv_func, (void *)password };
|
||||||
|
pam_handle_t *pamh = NULL;
|
||||||
|
|
||||||
|
int retval = pam_start("login", username, &conv, &pamh);
|
||||||
|
if (retval != PAM_SUCCESS) return retval;
|
||||||
|
|
||||||
|
retval = pam_authenticate(pamh, 0);
|
||||||
|
pam_end(pamh, retval);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
32
src/pam.go
Executable file
32
src/pam.go
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo CFLAGS: -std=c11
|
||||||
|
#cgo LDFLAGS: -lpam
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
|
||||||
|
extern int authenticate_pam(const char *username, const char *password);
|
||||||
|
|
||||||
|
// Define PAM_SUCCESS explicitly for cgo
|
||||||
|
static const int CGO_PAM_SUCCESS = PAM_SUCCESS;
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authenticate using PAM (directly linked C function)
|
||||||
|
func PAMAuthenticate(username, password string) bool {
|
||||||
|
cUsername := C.CString(username)
|
||||||
|
cPassword := C.CString(password)
|
||||||
|
defer C.free(unsafe.Pointer(cUsername))
|
||||||
|
defer C.free(unsafe.Pointer(cPassword))
|
||||||
|
|
||||||
|
result := C.authenticate_pam(cUsername, cPassword)
|
||||||
|
return result == C.PAM_SUCCESS
|
||||||
|
//return result == C.CGO_PAM_SUCCESS
|
||||||
|
}
|
6
src/pam_darwin.go
Normal file
6
src/pam_darwin.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=mIfzwTfXnWI
|
||||||
|
func PAMAuthenticate(username, password string) bool {
|
||||||
|
return true
|
||||||
|
}
|
53
src/shadow.go
Normal file
53
src/shadow.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lcrypt
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <crypt.h>
|
||||||
|
#include <shadow.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int check_password(const char* username, const char* password) {
|
||||||
|
struct spwd *shadow_entry = getspnam(username);
|
||||||
|
if (!shadow_entry) {
|
||||||
|
return -1; // User not found or error
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *shadow_hash = shadow_entry->sp_pwdp;
|
||||||
|
if (!shadow_hash) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *calculated_hash = crypt(password, shadow_hash);
|
||||||
|
if (!calculated_hash) {
|
||||||
|
return -3; // Error in crypt
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcmp(shadow_hash, calculated_hash) == 0;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
//"os"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkPassword(username, password string) (bool, error) {
|
||||||
|
cUsername := C.CString(username)
|
||||||
|
cPassword := C.CString(password)
|
||||||
|
defer C.free(unsafe.Pointer(cUsername))
|
||||||
|
defer C.free(unsafe.Pointer(cPassword))
|
||||||
|
|
||||||
|
result := C.check_password(cUsername, cPassword)
|
||||||
|
switch result {
|
||||||
|
case 1: return true, nil
|
||||||
|
case 0: return false, nil
|
||||||
|
default: return false, fmt.Errorf("Auth Error")
|
||||||
|
}
|
||||||
|
}
|
30
src/unix.go
Normal file
30
src/unix.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startUnixSocket() {
|
||||||
|
socketPath := "/run/webdav.sock"
|
||||||
|
|
||||||
|
// Remove the socket if it exists
|
||||||
|
if err := os.RemoveAll(socketPath); err != nil {
|
||||||
|
log.Fatalf("Failed to remove old socket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to listen on socket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make socket accessible to Nginx
|
||||||
|
if err := os.Chmod(socketPath, 0666); err != nil {
|
||||||
|
log.Fatalf("Failed to chmod socket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("WebDAV server listening on %s\n", socketPath)
|
||||||
|
log.Fatal(http.Serve(listener, nil))
|
||||||
|
}
|
25
templates/index.html
Normal file
25
templates/index.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>File List</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{if .IsLoggedIn}}
|
||||||
|
<p>Welcome back!</p>
|
||||||
|
{{else}}
|
||||||
|
<p>Please log in to access more features.</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<h2>Files</h2>
|
||||||
|
{{if .Files}}
|
||||||
|
<ul>
|
||||||
|
{{range .Files}}
|
||||||
|
<li>{{.FileName}}</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p>No files available.</p>
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue