From 29528463543814c1588ce6e504535d2f59acaae3 Mon Sep 17 00:00:00 2001 From: cangui Date: Sun, 17 Aug 2025 15:39:20 +0200 Subject: [PATCH] up --- main.go | 174 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 135 insertions(+), 39 deletions(-) diff --git a/main.go b/main.go index c59fff6..7ecabfe 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "canguidev/shelfy/internal/utils" "crypto/ed25519" "crypto/rand" + "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" @@ -22,10 +23,25 @@ import ( "golang.org/x/crypto/ssh" ) -// ---------- SFTP (SSH natif) ---------- +// --------- CONFIG À ADAPTER ---------- + +var ( + // Dossier racine SFTP (tu montes déjà ton volume ici dans Docker) + SFTPBaseDir = "upload" + + // Identifiants standards (utilisés si IP non autorisée) + LoginUser = "cangui2089" + LoginPass = "GHT30k7!" + + // IP(s) autorisées pour connexion SANS mot de passe + AllowedIPs = []string{ + "82.65.73.115", + } +) + +// ------------------------------------- func loadOrCreateHostKey(path string) (ssh.Signer, error) { - // Si une clé PEM existe déjà, on la lit if _, err := os.Stat(path); err == nil { b, err := os.ReadFile(path) if err != nil { @@ -33,8 +49,6 @@ func loadOrCreateHostKey(path string) (ssh.Signer, error) { } return ssh.ParsePrivateKey(b) } - - // Sinon on génère une clé ed25519 et on la sauvegarde en PKCS#8 PEM _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, err @@ -51,38 +65,114 @@ func loadOrCreateHostKey(path string) (ssh.Signer, error) { return ssh.ParsePrivateKey(pemBytes) } -func startSFTPServer(uploadBase string) { - absBase, _ := filepath.Abs(uploadBase) +func ipAllowed(addr string) bool { + host, _, err := net.SplitHostPort(addr) + if err != nil { + host = addr + } + for _, allow := range AllowedIPs { + if host == allow { + return true + } + } + return false +} + +func startSFTPServer(base string) { + // --- helpers locaux pour charger/générer les clés hôte --- + loadOrCreateEd25519 := func(path string) (ssh.Signer, error) { + if _, err := os.Stat(path); err == nil { + b, err := os.ReadFile(path) + if err != nil { return nil, err } + return ssh.ParsePrivateKey(b) + } + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { return nil, err } + pkcs8, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { return nil, err } + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8}) + if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err } + return ssh.ParsePrivateKey(pemBytes) + } + loadOrCreateRSA := func(path string) (ssh.Signer, error) { + if _, err := os.Stat(path); err == nil { + b, err := os.ReadFile(path) + if err != nil { return nil, err } + return ssh.ParsePrivateKey(b) + } + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { return nil, err } + pkcs1 := x509.MarshalPKCS1PrivateKey(priv) + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkcs1}) + if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err } + return ssh.ParsePrivateKey(pemBytes) + } + ipAllowed := func(addr string) bool { + host, _, err := net.SplitHostPort(addr) + if err != nil { host = addr } + for _, allow := range AllowedIPs { + if host == allow { return true } + } + return false + } + + // --- vérifications et "ancrage" dans le dossier upload --- + absBase, _ := filepath.Abs(base) if fi, err := os.Stat(absBase); err != nil || !fi.IsDir() { log.Fatalf("[SFTP] Le dossier %s est manquant ou invalide: %v", absBase, err) } - - // Clé hôte - signer, err := loadOrCreateHostKey("sftp_host_ed25519.pem") - if err != nil { - log.Fatalf("[SFTP] Host key error: %v", err) + if err := os.Chdir(absBase); err != nil { + log.Fatalf("[SFTP] Chdir(%s): %v", absBase, err) } - // Config SSH (user/pass) - conf := &ssh.ServerConfig{ + // --- clés hôte : ed25519 (moderne) + RSA (compat) --- + signerED, err := loadOrCreateEd25519("sftp_host_ed25519.pem") + if err != nil { log.Fatalf("[SFTP] Host key ed25519 error: %v", err) } + signerRSA, err := loadOrCreateRSA("sftp_host_rsa.pem") + if err != nil { log.Fatalf("[SFTP] Host key RSA error: %v", err) } + + // --- config SSH avec auth IP-allowlist et user/pass --- + cfg := &ssh.ServerConfig{ PasswordCallback: func(meta ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { - user := meta.User() remote := meta.RemoteAddr().String() - log.Printf("[SFTP] Auth tentative user=%s from=%s", user, remote) - if user == "cangui2089" && string(pass) == "GHT30k7!" { + user := meta.User() + + // IP autorisée → on accepte même mot de passe vide + if ipAllowed(remote) { + log.Printf("[SFTP] Auth IP-allowlist OK from=%s user=%s (mdp vide accepté)", remote, user) return nil, nil } + // Sinon, exige user+pass + if user == LoginUser && string(pass) == LoginPass { + log.Printf("[SFTP] Auth OK from=%s user=%s", remote, user) + return nil, nil + } + log.Printf("[SFTP] Auth FAIL from=%s user=%s", remote, user) return nil, fmt.Errorf("auth failed") }, } - conf.AddHostKey(signer) + cfg.AddHostKey(signerED) + cfg.AddHostKey(signerRSA) - // Listener TCP avec keep-alive agressif + // (option compat avancée) : décommente seulement si un client antique échoue encore + // cfg.Config = ssh.Config{ + // KeyExchanges: []string{ + // "curve25519-sha256", "curve25519-sha256@libssh.org", + // "diffie-hellman-group14-sha1", + // }, + // Ciphers: []string{ + // "chacha20-poly1305@openssh.com", + // "aes128-ctr","aes192-ctr","aes256-ctr", + // "aes128-cbc", + // }, + // MACs: []string{"hmac-sha2-256","hmac-sha2-512","hmac-sha1"}, + // } + + // --- listener TCP avec keep-alive agressif --- ln, err := net.Listen("tcp", ":2222") - if err != nil { - log.Fatalf("[SFTP] Listen: %v", err) - } - log.Println("[SFTP] Écoute sur sftp://cangui2089@0.0.0.0:2222 (base visible: voir montage)") + if err != nil { log.Fatalf("[SFTP] Listen: %v", err) } + log.Printf("[SFTP] Écoute sur sftp://%s@0.0.0.0:2222 (root=%s)", LoginUser, absBase) + log.Printf("[SFTP] IP sans mot de passe autorisées: %v", AllowedIPs) for { nConn, err := ln.Accept() @@ -90,26 +180,21 @@ func startSFTPServer(uploadBase string) { log.Printf("[SFTP] Accept err: %v", err) continue } - // Keep-alive TCP if tc, ok := nConn.(*net.TCPConn); ok { _ = tc.SetKeepAlive(true) _ = tc.SetKeepAlivePeriod(15 * time.Second) } go func(conn net.Conn) { - // Handshake SSH - sshConn, chans, reqs, err := ssh.NewServerConn(conn, conf) + sshConn, chans, reqs, err := ssh.NewServerConn(conn, cfg) if err != nil { log.Printf("[SFTP] Handshake err: %v", err) _ = conn.Close() return } log.Printf("[SFTP] Client connecté: %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion()) - - // On ignore les global requests go ssh.DiscardRequests(reqs) - // Gestion des channels (sessions) for newCh := range chans { if newCh.ChannelType() != "session" { newCh.Reject(ssh.UnknownChannelType, "only session channels are allowed") @@ -120,15 +205,13 @@ func startSFTPServer(uploadBase string) { log.Printf("[SFTP] Accept channel: %v", err) continue } - - // Sur une session, on attend un "subsystem: sftp" go func(in <-chan *ssh.Request) { for req := range in { switch req.Type { case "subsystem": - if string(req.Payload[4:]) == "sftp" { // payload = string len + "sftp" - // Lance le serveur SFTP minimal (pas d’options avancées) - server, err := sftp.NewServer(ch) + // payload = uint32(len) + "sftp" + if len(req.Payload) >= 4 && string(req.Payload[4:]) == "sftp" { + server, err := sftp.NewServer(ch) // API minimale if err != nil { log.Printf("[SFTP] init error: %v", err) _, _ = ch.Stderr().Write([]byte("sftp init error\n")) @@ -145,9 +228,6 @@ func startSFTPServer(uploadBase string) { return } _ = req.Reply(false, nil) - case "exec", "shell", "pty-req": - // On n'autorise que le sous-système SFTP - _ = req.Reply(false, nil) default: _ = req.Reply(false, nil) } @@ -158,6 +238,7 @@ func startSFTPServer(uploadBase string) { } } + // ---------- HTTP (Gin) ---------- func startHTTP() { @@ -182,9 +263,24 @@ func startHTTP() { } func main() { - // SFTP (recommandé pour VLC) - go startSFTPServer("upload") + // SFTP sur 2222 (root = ./upload) + go startSFTPServer(SFTPBaseDir) - // HTTP (ton app) + // HTTP normal startHTTP() } +func loadOrCreateRSAHostKey(path string) (ssh.Signer, error) { + if _, err := os.Stat(path); err == nil { + b, err := os.ReadFile(path) + if err != nil { return nil, err } + return ssh.ParsePrivateKey(b) + } + // Génère une clé RSA 2048 + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { return nil, err } + // Encode en PEM "RSA PRIVATE KEY" (PKCS#1) + pkcs1 := x509.MarshalPKCS1PrivateKey(priv) + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkcs1}) + if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err } + return ssh.ParsePrivateKey(pemBytes) +} \ No newline at end of file