Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ jobs:

- name: Test
run: go test .
working-directory: .
working-directory: cmd/simplehttpserver/

- name: Build
run: go build .
working-directory: .
working-directory: cmd/simplehttpserver/
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
builds:
- binary: simplehttpserver
main: simplehttpserver.go
main: cmd/simplehttpserver/simplehttpserver.go
goos:
- linux
- windows
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM golang:1.14-alpine AS builder
FROM golang:1.16-alpine AS builder
RUN apk add --no-cache git
RUN GO111MODULE=auto go get -u -v github.com/projectdiscovery/simplehttpserver
RUN GO111MODULE=auto go get -u -v github.com/projectdiscovery/simplehttpserver/cmd/simplehttpserver

FROM alpine:latest
COPY --from=builder /go/bin/simplehttpserver /usr/local/bin/
Expand Down
5 changes: 0 additions & 5 deletions gen_cert.sh

This file was deleted.

24 changes: 24 additions & 0 deletions internal/runner/banner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package runner

import "github.com/projectdiscovery/gologger"

const banner = `
_ _ _ _ _
___(_)_ __ ___ _ __ | | ___| |__ | |_| |_ _ __ ___ ___ _ ____ _____ _ __
/ __| | '_ ' _ \| '_ \| |/ _ \ '_ \| __| __| '_ \/ __|/ _ \ '__\ \ / / _ \ '__|
\__ \ | | | | | | |_) | | __/ | | | |_| |_| |_) \__ \ __/ | \ V / __/ |
|___/_|_| |_| |_| .__/|_|\___|_| |_|\__|\__| .__/|___/\___|_| \_/ \___|_|
|_| |_|
`

// Version is the current version
const Version = `0.0.1`

// showBanner is used to show the banner to the user
func showBanner() {
gologger.Print().Msgf("%s\n", banner)
gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")

gologger.Print().Msgf("Use with caution. You are responsible for your actions\n")
gologger.Print().Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
}
2 changes: 2 additions & 0 deletions internal/runner/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package runner contains the internal logic
package runner
93 changes: 93 additions & 0 deletions internal/runner/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package runner

import (
"flag"
"os"
"strings"

"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
)

type Options struct {
ListenAddress string
Folder string
BasicAuth string
username string
password string
Realm string
TLSCertificate string
TLSKey string
TLSDomain string
HTTPS bool
Verbose bool
EnableUpload bool
EnableTCP bool
RulesFile string
TCPWithTLS bool
Version bool
Silent bool
}

// ParseOptions parses the command line options for application
func ParseOptions() *Options {
options := &Options{}
flag.StringVar(&options.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port")
flag.BoolVar(&options.EnableTCP, "tcp", false, "TCP Server")
flag.BoolVar(&options.TCPWithTLS, "tls", false, "Enable TCP TLS")
flag.StringVar(&options.RulesFile, "rules", "", "Rules yaml file")
flag.StringVar(&options.Folder, "path", ".", "Folder")
flag.BoolVar(&options.EnableUpload, "upload", false, "Enable upload via PUT")
flag.BoolVar(&options.HTTPS, "https", false, "HTTPS")
flag.StringVar(&options.TLSCertificate, "cert", "", "HTTPS Certificate")
flag.StringVar(&options.TLSKey, "key", "", "HTTPS Certificate Key")
flag.StringVar(&options.TLSDomain, "domain", "local.host", "Domain")
flag.BoolVar(&options.Verbose, "verbose", false, "Verbose")
flag.StringVar(&options.BasicAuth, "basic-auth", "", "Basic auth (username:password)")
flag.StringVar(&options.Realm, "realm", "Please enter username and password", "Realm")
flag.BoolVar(&options.Version, "version", false, "Show version of the software")
flag.BoolVar(&options.Silent, "silent", false, "Show only results in the output")

flag.Parse()

// Read the inputs and configure the logging
options.configureOutput()

showBanner()

if options.Version {
gologger.Info().Msgf("Current Version: %s\n", Version)
os.Exit(0)
}

options.validateOptions()

return options
}

func (options *Options) validateOptions() {
if flag.NArg() > 0 && options.Folder == "." {
options.Folder = flag.Args()[0]
}

if options.BasicAuth != "" {
baTokens := strings.SplitN(options.BasicAuth, ":", 2)
if len(baTokens) > 0 {
options.username = baTokens[0]
}
if len(baTokens) > 1 {
options.password = baTokens[1]
}
}
}

// configureOutput configures the output on the screen
func (options *Options) configureOutput() {
// If the user desires verbose output, show verbose output
if options.Verbose {
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
}
if options.Silent {
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
}
}
70 changes: 70 additions & 0 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package runner

import (
"github.com/projectdiscovery/simplehttpserver/pkg/httpserver"
"github.com/projectdiscovery/simplehttpserver/pkg/tcpserver"
)

// Runner is a client for running the enumeration process.
type Runner struct {
options *Options
serverTCP *tcpserver.TCPServer
httpServer *httpserver.HTTPServer
}

func New(options *Options) (*Runner, error) {
r := Runner{options: options}
if r.options.EnableTCP {
serverTCP, err := tcpserver.New(tcpserver.Options{
Listen: r.options.ListenAddress,
TLS: r.options.TCPWithTLS,
Domain: "local.host",
Verbose: r.options.Verbose,
})
if err != nil {
return nil, err
}
err = serverTCP.LoadTemplate(r.options.RulesFile)
if err != nil {
return nil, err
}
r.serverTCP = serverTCP
return &r, nil
}

httpServer, err := httpserver.New(&httpserver.Options{
Folder: r.options.Folder,
EnableUpload: r.options.EnableUpload,
ListenAddress: r.options.ListenAddress,
TLS: r.options.HTTPS,
Certificate: r.options.TLSCertificate,
CertificateKey: r.options.TLSKey,
CertificateDomain: r.options.TLSDomain,
BasicAuthUsername: r.options.username,
BasicAuthPassword: r.options.password,
BasicAuthReal: r.options.Realm,
Verbose: r.options.Verbose,
})
if err != nil {
return nil, err
}
r.httpServer = httpServer

return &r, nil
}

func (r *Runner) Run() error {
if r.options.EnableTCP {
return r.serverTCP.ListenAndServe()
}

if r.options.HTTPS {
return r.httpServer.ListenAndServeTLS()
}

return r.httpServer.ListenAndServe()
}

func (r *Runner) Close() error {
return nil
}
19 changes: 19 additions & 0 deletions pkg/httpserver/authlayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package httpserver

import (
"fmt"
"net/http"
)

func (t *HTTPServer) basicauthlayer(handler http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || user != t.options.BasicAuthUsername || pass != t.options.BasicAuthPassword {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", t.options.BasicAuthReal))
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized.\n")) //nolint
return
}
handler.ServeHTTP(w, r)
})
}
120 changes: 120 additions & 0 deletions pkg/httpserver/httpserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package httpserver

import (
"errors"
"net"
"net/http"
"os"
"runtime"
"strconv"
"syscall"

"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/simplehttpserver/pkg/sslcert"
)

type Options struct {
Folder string
EnableUpload bool
ListenAddress string
TLS bool
Certificate string
CertificateKey string
CertificateDomain string
BasicAuthUsername string
BasicAuthPassword string
BasicAuthReal string
Verbose bool
}

type HTTPServer struct {
options *Options
layers http.Handler
listener net.Listener
}

func New(options *Options) (*HTTPServer, error) {
var h HTTPServer
EnableUpload = options.EnableUpload
EnableVerbose = options.Verbose
layers := h.loglayer(http.FileServer(http.Dir(options.Folder)))
if options.BasicAuthUsername != "" || options.BasicAuthPassword != "" {
layers = h.loglayer(h.basicauthlayer(http.FileServer(http.Dir(options.Folder))))
}

return &HTTPServer{options: options, layers: layers}, nil
}

func (t *HTTPServer) ListenAndServe() error {
var err error
retry_listen:
gologger.Print().Msgf("Serving %s on http://%s/...", t.options.Folder, t.options.ListenAddress)
err = http.ListenAndServe(t.options.ListenAddress, t.layers)
if err != nil {
if isErrorAddressAlreadyInUse(err) {
gologger.Print().Msgf("Can't listen on %s: %s - retrying with another port\n", t.options.ListenAddress, err)
newListenAddress, err := incPort(t.options.ListenAddress)
if err != nil {
return err
}
t.options.ListenAddress = newListenAddress
goto retry_listen
}
}
return nil
}

func (t *HTTPServer) ListenAndServeTLS() error {
gologger.Print().Msgf("Serving %s on https://%s/...", t.options.Folder, t.options.ListenAddress)
if t.options.Certificate == "" || t.options.CertificateKey == "" {
tlsOptions := sslcert.DefaultOptions
tlsOptions.Host = t.options.CertificateDomain
tlsConfig, err := sslcert.NewTLSConfig(tlsOptions)
if err != nil {
return err
}
httpServer := &http.Server{
Addr: t.options.ListenAddress,
TLSConfig: tlsConfig,
}
httpServer.Handler = t.layers
return httpServer.ListenAndServeTLS("", "")
}
return http.ListenAndServeTLS(t.options.ListenAddress, t.options.Certificate, t.options.CertificateKey, t.layers)
}

func isErrorAddressAlreadyInUse(err error) bool {
var eOsSyscall *os.SyscallError
if !errors.As(err, &eOsSyscall) {
return false
}
var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr)
if !errors.As(eOsSyscall, &errErrno) {
return false
}
if errErrno == syscall.EADDRINUSE {
return true
}
const WSAEADDRINUSE = 10048
if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE {
return true
}
return false
}

func incPort(address string) (string, error) {
addrOrig, portOrig, err := net.SplitHostPort(address)
if err != nil {
return address, err
}

// increment port
portNumber, err := strconv.Atoi(portOrig)
if err != nil {
return address, err
}
portNumber++
newPort := strconv.FormatInt(int64(portNumber), 10)

return net.JoinHostPort(addrOrig, newPort), nil
}
Loading