aboutsummaryrefslogblamecommitdiff
path: root/main.go
blob: cf7e8a151d6543c006c664adf1dc6de2bb989c4e (plain) (tree)
1
2
3
4
5
6
7
8
9


            

                 

                   
             
             
                  
            





                                                      

 
             
                                              
                                                             

                           
                    



                                                                       
 

                                


                                   
                    








                                                         
 

                               
                                                  






                                                                                                   
                                                                                    


































                                                                                 
                                                                               









                                                                        
                      






















                                                                                          
                              





















                                                                                      
                                       







                                                                                              
                                                     


                                                                

                                        










                                                                           
                                                  

                                                        





                                                           
                                                           




                                                                        
                                                                   



                                                                            


                                          
                  
 
                                                                           


                                                                       

         
package main

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"

	"git.deuxfleurs.fr/Deuxfleurs/bagage/s3"
	"git.deuxfleurs.fr/Deuxfleurs/bagage/sftp"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"golang.org/x/crypto/ssh"
)

func main() {
	log.Println("=== Starting Bagage ===")
	config := (&Config{}).LoadWithDefault().LoadWithEnv()
	log.Println(config)

	// Some init
	err := os.MkdirAll(config.S3Cache, 0755)
	if err != nil {
		log.Fatalf("init failed: mkdir s3 cache failed: ", err)
	}

	// Launch our submodules
	done := make(chan error)
	go httpServer(config, done)
	go sshServer(config, done)

	err = <-done
	if err != nil {
		log.Fatalf("A component failed: %v", err)
	}
}

type s3creds struct {
	accessKey string
	secretKey string
}

var keychain map[string]s3creds

func sshServer(dconfig *Config, done chan error) {
	keychain = make(map[string]s3creds)

	config := &ssh.ServerConfig{
		PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
			log.Printf("Login: %s\n", c.User())
			access_key, secret_key, err := LdapGetS3(dconfig, c.User(), string(pass))
			if err == nil {
				keychain[c.User()] = s3creds{access_key, secret_key}
			}
			return nil, err
		},
	}

	privateBytes, err := ioutil.ReadFile(dconfig.SSHKey)
	if err != nil {
		log.Fatal("Failed to load private key", err)
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		log.Fatal("Failed to parse private key", err)
	}

	config.AddHostKey(private)

	// Once a ServerConfig has been configured, connections can be
	// accepted.
	listener, err := net.Listen("tcp", "0.0.0.0:2222")
	if err != nil {
		log.Fatal("failed to listen for connection", err)
	}
	log.Printf("Listening on %v\n", listener.Addr())

	for {
		nConn, err := listener.Accept()
		if err != nil {
			log.Printf("failed to accept incoming connection: ", err)
			continue
		}
		go handleSSHConn(nConn, dconfig, config)
	}
}

func handleSSHConn(nConn net.Conn, dconfig *Config, config *ssh.ServerConfig) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	defer nConn.Close()

	// Before use, a handshake must be performed on the incoming
	// net.Conn.
	serverConn, chans, reqs, err := ssh.NewServerConn(nConn, config)
	if err != nil {
		log.Printf("failed to handshake: ", err)
		return
	}
	defer serverConn.Conn.Close()
	user := serverConn.Conn.User()
	log.Printf("SSH connection established for %v\n", user)

	// The incoming Request channel must be serviced.
	go ssh.DiscardRequests(reqs)

	// Service the incoming Channel channel.
	for newChannel := range chans {
		// Channels have a type, depending on the application level
		// protocol intended. In the case of an SFTP session, this is "subsystem"
		// with a payload string of "<length=4>sftp"
		log.Printf("Incoming channel: %s\n", newChannel.ChannelType())
		if newChannel.ChannelType() != "session" {
			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
			log.Printf("Unknown channel type: %s\n", newChannel.ChannelType())
			continue
		}

		channel, requests, err := newChannel.Accept()
		if err != nil {
			log.Print("could not accept channel.", err)
			return
		}
		log.Printf("Channel accepted\n")

		// Sessions have out-of-band requests such as "shell",
		// "pty-req" and "env".  Here we handle only the
		// "subsystem" request.
		go func(in <-chan *ssh.Request) {
			for req := range in {
				log.Printf("Request: %v\n", req.Type)
				ok := false
				switch req.Type {
				case "subsystem":
					log.Printf("Subsystem: %s\n", req.Payload[4:])
					if string(req.Payload[4:]) == "sftp" {
						ok = true
					}
				}
				log.Printf(" - accepted: %v\n", ok)
				req.Reply(ok, nil)
			}
		}(requests)

		creds := keychain[user]
		mc, err := minio.New(dconfig.Endpoint, &minio.Options{
			Creds:  credentials.NewStaticV4(creds.accessKey, creds.secretKey, ""),
			Secure: dconfig.UseSSL,
		})
		if err != nil {
			return
		}

		fs := s3.NewS3FS(mc, dconfig.S3Cache)
		server, err := sftp.NewServer(ctx, channel, &fs)

		if err != nil {
			log.Println(err)
			return
		}

		if err := server.Serve(); err == io.EOF {
			server.Close()
			log.Print("sftp client exited session.")
		} else if err != nil {
			log.Print("sftp server completed with error:", err)
		}
	}
}

func httpServer(config *Config, done chan error) {
	// Assemble components to handle WebDAV requests
	http.Handle(config.DavPath+"/",
		CorsAllowAllOrigins{
			AndThen: BasicAuthExtract{
				OnNotFound: OptionsNoError{
					NotAuthorized{},
				},
				OnCreds: LdapPreAuth{
					WithConfig: config,
					OnWrongPassword: OptionsNoError{
						Error: NotAuthorized{},
					},
					OnFailure: InternalError{},
					OnCreds: S3Auth{
						WithConfig: config,
						OnFailure:  InternalError{},
						OnMinioClient: WebDav{
							WithConfig: config,
						},
					},
				},
			},
		})

	if err := http.ListenAndServe(config.HttpListen, nil); err != nil {
		done <- fmt.Errorf("Error with WebDAV server: %v", err)
	} else {
		done <- nil
	}
}