2017-10-20 15:08:25 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2017-10-28 20:40:24 +00:00
|
|
|
"bufio"
|
2017-11-08 13:31:26 +00:00
|
|
|
"fmt"
|
2017-10-20 15:08:25 +00:00
|
|
|
"io"
|
|
|
|
"log"
|
2017-10-28 20:40:24 +00:00
|
|
|
"net"
|
2017-11-26 19:57:57 +00:00
|
|
|
|
2017-11-30 21:28:18 +00:00
|
|
|
"os"
|
|
|
|
|
|
|
|
"time"
|
|
|
|
|
2017-12-02 17:16:15 +00:00
|
|
|
"os/signal"
|
|
|
|
"syscall"
|
|
|
|
|
2017-12-02 17:59:09 +00:00
|
|
|
"sync"
|
|
|
|
|
2018-10-22 19:31:37 +00:00
|
|
|
"github.com/dnutiu/simplFT/server/config"
|
2017-11-26 20:06:45 +00:00
|
|
|
"github.com/fsnotify/fsnotify"
|
2017-11-26 19:57:57 +00:00
|
|
|
"github.com/spf13/viper"
|
2017-10-20 15:08:25 +00:00
|
|
|
)
|
|
|
|
|
2018-01-01 14:21:46 +00:00
|
|
|
// ConfigPath is used by the config package to find the path of the config file.
|
2017-11-30 21:28:18 +00:00
|
|
|
var ConfigPath string
|
|
|
|
|
2018-01-01 14:21:46 +00:00
|
|
|
// ConfigName is used by the config package to find the config file.
|
|
|
|
var ConfigName string
|
|
|
|
|
2017-11-30 21:28:18 +00:00
|
|
|
// uploadDirectory is the directory where the files will be uploaded
|
|
|
|
var uploadDirectory string
|
|
|
|
|
|
|
|
// uploadTimeout is the amount in seconds the server will wait for a file to be uploaded
|
|
|
|
var uploadTimeout time.Duration
|
|
|
|
|
2017-12-15 19:34:39 +00:00
|
|
|
// uploadEnabled holds true of false, if in the config upload was enabled when the upload server was started.
|
|
|
|
var uploadEnabled bool
|
|
|
|
|
2017-12-02 17:16:15 +00:00
|
|
|
// Shutdown is the shutdown where SIGINT and SIGTERM is send too
|
2017-12-15 19:32:22 +00:00
|
|
|
var Shutdown = make(chan os.Signal)
|
|
|
|
var ftpShutdown = make(chan struct{})
|
|
|
|
var uploadShutdown = make(chan struct{})
|
2017-12-02 17:16:15 +00:00
|
|
|
|
|
|
|
var uploadListener net.Listener
|
|
|
|
var ftpListener net.Listener
|
|
|
|
|
2017-12-02 17:59:09 +00:00
|
|
|
// All connected clients
|
|
|
|
var clients map[Client]bool
|
|
|
|
|
2017-11-08 13:11:37 +00:00
|
|
|
// Client interface provides the blueprints for the Client that is used by the server.
|
|
|
|
type Client interface {
|
|
|
|
Connection() net.Conn // Connection returns the connection stream.
|
|
|
|
SetConnection(conn net.Conn) // SetConnection sets the connection for the client.
|
|
|
|
Disconnect() // Disconnect closes the Client's connections and clears up resources.
|
2017-11-23 19:18:20 +00:00
|
|
|
Stack() *StringStack // Returns the underlying String Stack.
|
2017-11-08 13:11:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FTPClient represents a FTPClient connection, it holds a root cage and the underlying connection.
|
|
|
|
type FTPClient struct {
|
|
|
|
rootCage *StringStack // rootCage is a StringStack that is used to represent the current directory the client is in.
|
|
|
|
connection net.Conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stack returns the root cage stack.
|
2017-11-23 19:18:20 +00:00
|
|
|
func (c *FTPClient) Stack() *StringStack {
|
2017-11-08 13:11:37 +00:00
|
|
|
return c.rootCage
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetStack sets the stack for the FTPClient.
|
|
|
|
func (c *FTPClient) SetStack(stack *StringStack) {
|
|
|
|
c.rootCage = stack
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connection returns the Connection of the client.
|
|
|
|
func (c *FTPClient) Connection() net.Conn {
|
|
|
|
return c.connection
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetConnection sets the given connection to the client.
|
|
|
|
func (c *FTPClient) SetConnection(conn net.Conn) {
|
|
|
|
c.connection = conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disconnects the client.
|
|
|
|
func (c *FTPClient) Disconnect() {
|
|
|
|
c.connection.Close()
|
|
|
|
}
|
|
|
|
|
2017-12-02 17:16:15 +00:00
|
|
|
func shutdownHandler() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-Shutdown:
|
|
|
|
log.Println("Shutdown signal received")
|
2017-12-02 17:59:09 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
go func() { // Disconnect all the clients.
|
|
|
|
for k := range clients {
|
|
|
|
k.Disconnect()
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
|
2017-12-15 19:25:55 +00:00
|
|
|
ShutdownUploadServer()
|
2017-12-15 19:32:22 +00:00
|
|
|
ShutdownFtpServer()
|
2017-12-02 17:16:15 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 17:59:09 +00:00
|
|
|
func ShutdownUploadServer() {
|
2017-12-15 19:34:39 +00:00
|
|
|
if uploadEnabled == true {
|
2017-12-15 19:32:22 +00:00
|
|
|
if uploadListener != nil {
|
|
|
|
uploadListener.Close()
|
|
|
|
}
|
|
|
|
uploadShutdown <- struct{}{}
|
2017-12-02 17:59:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ShutdownFtpServer() {
|
|
|
|
if ftpListener != nil {
|
|
|
|
ftpListener.Close()
|
|
|
|
}
|
2017-12-15 19:25:02 +00:00
|
|
|
ftpShutdown <- struct{}{}
|
2017-12-02 17:59:09 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 17:16:15 +00:00
|
|
|
func Init() {
|
|
|
|
signal.Notify(Shutdown, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
|
2017-12-02 17:59:09 +00:00
|
|
|
clients = make(map[Client]bool)
|
2017-12-02 17:16:15 +00:00
|
|
|
go shutdownHandler()
|
|
|
|
|
2018-01-01 14:21:46 +00:00
|
|
|
config.InitializeConfiguration(ConfigName, ConfigPath)
|
2017-12-02 17:16:15 +00:00
|
|
|
config.ChangeCallback(func(event fsnotify.Event) {
|
|
|
|
log.Println("Configuration reloaded successfully!")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-11-08 13:11:37 +00:00
|
|
|
func HandleConnection(client Client) {
|
|
|
|
defer client.Disconnect()
|
2017-11-08 13:31:26 +00:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
log.Println("PANIC: ", r)
|
|
|
|
|
|
|
|
recoveryError, ok := r.(string)
|
|
|
|
if ok {
|
|
|
|
io.WriteString(client.Connection(), fmt.Sprintf("PANIC: %s", recoveryError))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-11-08 13:11:37 +00:00
|
|
|
log.Println(client.Connection().RemoteAddr(), "has connected.")
|
2017-12-02 17:59:09 +00:00
|
|
|
clients[client] = true
|
2017-10-20 15:08:25 +00:00
|
|
|
|
|
|
|
// Process input
|
2017-11-08 13:11:37 +00:00
|
|
|
input := bufio.NewScanner(client.Connection())
|
2017-11-09 21:03:00 +00:00
|
|
|
|
2017-10-20 15:08:25 +00:00
|
|
|
for input.Scan() {
|
2017-11-08 13:11:37 +00:00
|
|
|
log.Println(client.Connection().RemoteAddr(), ":", input.Text())
|
2017-10-20 15:08:25 +00:00
|
|
|
|
2017-11-08 13:11:37 +00:00
|
|
|
err := ProcessInput(client, input.Text())
|
2017-10-20 15:08:25 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
2017-11-08 13:11:37 +00:00
|
|
|
io.WriteString(client.Connection(), err.Error()+"\n")
|
2017-10-20 15:08:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client has left.
|
2017-12-02 17:59:09 +00:00
|
|
|
delete(clients, client)
|
2017-11-08 13:11:37 +00:00
|
|
|
log.Println(client.Connection().RemoteAddr(), "has disconnected.")
|
2017-10-20 15:08:25 +00:00
|
|
|
}
|
2017-11-26 19:57:57 +00:00
|
|
|
|
2017-12-15 19:25:02 +00:00
|
|
|
func StartFtpServer(wg *sync.WaitGroup) error {
|
|
|
|
defer wg.Done()
|
2017-11-26 19:57:57 +00:00
|
|
|
Addr := viper.GetString("address")
|
2017-11-26 21:03:15 +00:00
|
|
|
Port := viper.GetInt("port")
|
2017-11-26 19:57:57 +00:00
|
|
|
DirDepth := viper.GetInt("maxDirDepth")
|
2017-11-26 20:06:45 +00:00
|
|
|
BasePath = viper.GetString("absoluteServePath")
|
2017-11-26 19:57:57 +00:00
|
|
|
|
|
|
|
// Start the server
|
2017-12-02 17:16:15 +00:00
|
|
|
var err error
|
|
|
|
ftpListener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", Addr, Port))
|
2017-11-26 19:57:57 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
2017-12-02 17:16:15 +00:00
|
|
|
return err
|
2017-11-26 19:57:57 +00:00
|
|
|
}
|
2017-12-02 17:16:15 +00:00
|
|
|
defer ftpListener.Close()
|
2017-11-26 19:57:57 +00:00
|
|
|
|
|
|
|
log.Println("Hello world!")
|
|
|
|
log.Println("Ftp server running on:", Addr, "port", Port)
|
|
|
|
|
|
|
|
for {
|
2017-12-15 19:25:02 +00:00
|
|
|
conn, err := ftpListener.Accept()
|
|
|
|
|
2017-12-02 17:59:09 +00:00
|
|
|
// Handle shutdown
|
|
|
|
select {
|
|
|
|
case <-ftpShutdown:
|
2017-12-15 19:25:02 +00:00
|
|
|
goto exit
|
2017-12-02 17:59:09 +00:00
|
|
|
default:
|
2017-12-15 19:25:02 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
continue
|
|
|
|
}
|
2017-11-26 19:57:57 +00:00
|
|
|
|
2017-12-15 19:25:02 +00:00
|
|
|
client := FTPClient{}
|
|
|
|
client.SetStack(MakeStringStack(DirDepth))
|
|
|
|
client.SetConnection(conn)
|
2017-11-26 19:57:57 +00:00
|
|
|
|
2017-12-15 19:25:02 +00:00
|
|
|
go HandleConnection(&client)
|
|
|
|
}
|
2017-11-26 19:57:57 +00:00
|
|
|
}
|
2017-12-15 19:25:02 +00:00
|
|
|
exit:
|
|
|
|
log.Println("Ftp server exited.")
|
2017-12-02 17:16:15 +00:00
|
|
|
return nil
|
2017-11-26 19:57:57 +00:00
|
|
|
}
|
2017-11-26 21:03:15 +00:00
|
|
|
|
2017-11-30 21:28:18 +00:00
|
|
|
func HandleUpload(conn net.Conn) {
|
|
|
|
// Initialize Client
|
|
|
|
client := FTPClient{}
|
|
|
|
client.SetStack(MakeStringStack(2))
|
|
|
|
|
|
|
|
// Upload directory
|
|
|
|
client.Stack().Push(uploadDirectory)
|
|
|
|
client.SetConnection(conn)
|
2017-12-02 17:59:09 +00:00
|
|
|
defer client.Disconnect()
|
2017-11-30 21:28:18 +00:00
|
|
|
|
|
|
|
// Create the file on the disk and make sure that the filename is random.
|
|
|
|
var filename = randSeq(10)
|
|
|
|
var _, err = os.Stat(MakePathFromStringStack(client.Stack()) + filename)
|
|
|
|
|
|
|
|
for !os.IsNotExist(err) {
|
|
|
|
filename = randSeq(10)
|
|
|
|
_, err = os.Stat(MakePathFromStringStack(client.Stack()) + filename)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This channel will be used to store the uploadResult
|
2017-11-30 22:17:35 +00:00
|
|
|
c1 := make(chan error, 1)
|
2017-11-30 21:28:18 +00:00
|
|
|
log.Println(conn.RemoteAddr().String() + " is uploading something.")
|
2017-12-02 17:59:09 +00:00
|
|
|
clients[&client] = true
|
2017-11-30 21:28:18 +00:00
|
|
|
|
|
|
|
// Create a new Go routine for uploading
|
|
|
|
go func() {
|
2017-11-30 22:17:35 +00:00
|
|
|
err := UploadFile(&client, filename)
|
|
|
|
c1 <- err
|
2017-11-30 21:28:18 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for either UploadResult or Timeout
|
|
|
|
select {
|
|
|
|
case result := <-c1:
|
|
|
|
{
|
2017-11-30 22:17:35 +00:00
|
|
|
if result == nil {
|
2017-11-30 21:28:18 +00:00
|
|
|
io.WriteString(conn, filename)
|
|
|
|
log.Println(conn.RemoteAddr().String() + "'s upload finished.")
|
|
|
|
} else {
|
2017-11-30 22:17:35 +00:00
|
|
|
log.Println(fmt.Sprintf("%s: %s %s", "HandleUpload", conn.RemoteAddr().String(), result.Error()))
|
2017-11-30 21:28:18 +00:00
|
|
|
|
|
|
|
client.Stack().Push(filename)
|
|
|
|
os.Remove(MakePathFromStringStack(client.Stack()))
|
|
|
|
|
2017-11-30 22:17:35 +00:00
|
|
|
io.WriteString(conn, result.Error())
|
2017-11-30 21:28:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
case <-time.After(time.Second * uploadTimeout):
|
|
|
|
{
|
|
|
|
io.WriteString(conn, "Timeout")
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
}
|
2017-12-02 17:59:09 +00:00
|
|
|
|
|
|
|
delete(clients, &client)
|
2017-11-30 21:28:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StartUploadServer starts the uploading server
|
2017-12-15 19:25:02 +00:00
|
|
|
func StartUploadServer(wg *sync.WaitGroup) error {
|
|
|
|
defer wg.Done()
|
|
|
|
var err error
|
2017-12-15 19:34:39 +00:00
|
|
|
uploadEnabled = viper.GetBool("upload.enabled")
|
|
|
|
if uploadEnabled == false {
|
2017-11-26 21:03:15 +00:00
|
|
|
log.Println("Uploading not enabled. To enable modify the config file and restart the server")
|
2017-11-30 21:28:18 +00:00
|
|
|
return ErrUploadServerFailure
|
2017-11-26 21:03:15 +00:00
|
|
|
}
|
|
|
|
|
2017-11-30 21:28:18 +00:00
|
|
|
addr := viper.GetString("upload.address")
|
|
|
|
port := viper.GetInt("upload.port")
|
|
|
|
uploadDirectory = viper.GetString("upload.directory")
|
|
|
|
uploadTimeout = time.Duration(viper.GetInt("upload.timeout"))
|
2017-11-26 21:03:15 +00:00
|
|
|
|
2017-12-15 19:25:02 +00:00
|
|
|
uploadListener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
2017-11-26 21:03:15 +00:00
|
|
|
if err != nil {
|
2017-11-30 21:28:18 +00:00
|
|
|
log.Println(err)
|
2017-12-22 16:09:33 +00:00
|
|
|
goto exit
|
2017-11-30 21:28:18 +00:00
|
|
|
}
|
2017-12-02 17:16:15 +00:00
|
|
|
defer uploadListener.Close()
|
2017-11-30 21:28:18 +00:00
|
|
|
|
2017-12-22 16:09:33 +00:00
|
|
|
err = os.Mkdir(BasePath+"/"+uploadDirectory, 0740)
|
2017-11-30 21:28:18 +00:00
|
|
|
if err != nil {
|
2017-12-22 16:09:33 +00:00
|
|
|
if _, err := os.Stat(BasePath + "/" + uploadDirectory); err != nil {
|
2017-11-30 21:28:18 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
log.Println("Can't create upload directory!")
|
2017-12-22 16:09:33 +00:00
|
|
|
goto exit
|
2017-11-30 21:28:18 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-26 21:03:15 +00:00
|
|
|
}
|
|
|
|
|
2017-11-30 21:28:18 +00:00
|
|
|
log.Println("Upload server running on:", addr, "port", port)
|
2017-11-26 21:03:15 +00:00
|
|
|
|
|
|
|
for {
|
2017-12-15 19:25:02 +00:00
|
|
|
conn, err := uploadListener.Accept()
|
2017-12-02 17:59:09 +00:00
|
|
|
// Handle shutdown
|
|
|
|
select {
|
|
|
|
case <-uploadShutdown:
|
2017-12-15 19:25:02 +00:00
|
|
|
goto exit
|
2017-12-02 17:59:09 +00:00
|
|
|
default:
|
2017-12-15 19:25:02 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
continue
|
|
|
|
}
|
2017-12-02 17:59:09 +00:00
|
|
|
|
2017-12-15 19:25:02 +00:00
|
|
|
go HandleUpload(conn)
|
2017-11-26 21:03:15 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-30 21:28:18 +00:00
|
|
|
|
2017-12-15 19:25:02 +00:00
|
|
|
exit:
|
|
|
|
log.Println("Upload server exited.")
|
2017-11-30 21:28:18 +00:00
|
|
|
return nil
|
2017-11-26 21:03:15 +00:00
|
|
|
}
|