Skip to content

Commit c8f58a6

Browse files
authored
Merge branch 'dev' into feature-generating-self-signed-certificates
2 parents bd654bf + 0dfe704 commit c8f58a6

File tree

6 files changed

+263
-13
lines changed

6 files changed

+263
-13
lines changed

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ module github.com/projectdiscovery/simplehttpserver
22

33
go 1.15
44

5-
require github.com/projectdiscovery/gologger v1.1.4
5+
require (
6+
github.com/projectdiscovery/gologger v1.1.4
7+
gopkg.in/yaml.v2 v2.4.0
8+
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
3030
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3131
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
3232
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
33+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
34+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
3335
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3436
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
3537
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/tcpserver/responseengine.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package tcpserver
2+
3+
import (
4+
"errors"
5+
)
6+
7+
func (t *TCPServer) BuildResponse(data []byte) ([]byte, error) {
8+
// Process all the rules
9+
for _, rule := range t.options.rules {
10+
if rule.matchRegex.Match(data) {
11+
return []byte(rule.Response), nil
12+
}
13+
}
14+
return nil, errors.New("No matched rule")
15+
}

pkg/tcpserver/rule.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tcpserver
2+
3+
import "regexp"
4+
5+
type RulesConfiguration struct {
6+
Rules []Rule `yaml:"rules"`
7+
}
8+
9+
type Rule struct {
10+
Match string `yaml:"match,omitempty"`
11+
matchRegex *regexp.Regexp
12+
Response string `yaml:"response,omitempty"`
13+
}
14+
15+
func NewRule(match string, response string) (*Rule, error) {
16+
regxp, err := regexp.Compile(match)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
return &Rule{Match: match, matchRegex: regxp, Response: response}, nil
22+
}

pkg/tcpserver/tcpserver.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package tcpserver
2+
3+
import (
4+
"crypto/tls"
5+
"io/ioutil"
6+
"net"
7+
"time"
8+
9+
"github.com/projectdiscovery/simplehttpserver/pkg/sslcert"
10+
"gopkg.in/yaml.v2"
11+
)
12+
13+
type Options struct {
14+
Listen string
15+
TLS bool
16+
Certificate string
17+
Key string
18+
Domain string
19+
rules []Rule
20+
}
21+
22+
type TCPServer struct {
23+
options Options
24+
listener net.Listener
25+
}
26+
27+
func New(options Options) (*TCPServer, error) {
28+
return &TCPServer{options: options}, nil
29+
}
30+
31+
func (t *TCPServer) AddRule(rule Rule) error {
32+
t.options.rules = append(t.options.rules, rule)
33+
return nil
34+
}
35+
36+
func (t *TCPServer) ListenAndServe() error {
37+
listener, err := net.Listen("tcp4", t.options.Listen)
38+
if err != nil {
39+
return err
40+
}
41+
t.listener = listener
42+
return t.run()
43+
}
44+
45+
func (t *TCPServer) handleConnection(conn net.Conn) error {
46+
defer conn.Close()
47+
48+
buf := make([]byte, 4096)
49+
for {
50+
conn.SetReadDeadline(time.Now().Add(time.Duration(5 * time.Second)))
51+
_, err := conn.Read(buf)
52+
if err != nil {
53+
return err
54+
}
55+
56+
resp, err := t.BuildResponse(buf)
57+
if err != nil {
58+
return err
59+
}
60+
61+
conn.Write(resp)
62+
}
63+
}
64+
65+
func (t *TCPServer) ListenAndServeTLS() error {
66+
var tlsConfig *tls.Config
67+
if t.options.Certificate != "" && t.options.Key != "" {
68+
cert, err := tls.LoadX509KeyPair(t.options.Certificate, t.options.Key)
69+
if err != nil {
70+
return err
71+
}
72+
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
73+
} else {
74+
tlsOptions := sslcert.DefaultOptions
75+
tlsOptions.Host = t.options.Domain
76+
cfg, err := sslcert.NewTLSConfig(tlsOptions)
77+
if err != nil {
78+
return err
79+
}
80+
tlsConfig = cfg
81+
}
82+
83+
listener, err := tls.Listen("tcp", t.options.Listen, tlsConfig)
84+
if err != nil {
85+
return err
86+
}
87+
t.listener = listener
88+
return t.run()
89+
}
90+
91+
func (t *TCPServer) run() error {
92+
for {
93+
c, err := t.listener.Accept()
94+
if err != nil {
95+
return err
96+
}
97+
go t.handleConnection(c)
98+
}
99+
}
100+
101+
func (t *TCPServer) Close() error {
102+
return t.listener.Close()
103+
}
104+
105+
func (t *TCPServer) LoadTemplate(templatePath string) error {
106+
var config RulesConfiguration
107+
yamlFile, err := ioutil.ReadFile(templatePath)
108+
if err != nil {
109+
return err
110+
}
111+
err = yaml.Unmarshal(yamlFile, &config)
112+
if err != nil {
113+
return err
114+
}
115+
116+
for _, ruleTemplate := range config.Rules {
117+
rule, err := NewRule(ruleTemplate.Match, ruleTemplate.Response)
118+
if err != nil {
119+
return err
120+
}
121+
t.options.rules = append(t.options.rules, *rule)
122+
}
123+
124+
return nil
125+
}

simplehttpserver.go

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,58 @@ package main
22

33
import (
44
"bytes"
5+
"errors"
56
"flag"
67
"fmt"
78
"io/ioutil"
8-
"log"
9+
"net"
910
"net/http"
1011
"net/http/httputil"
12+
"os"
1113
"path"
14+
"runtime"
15+
"strconv"
16+
"strings"
17+
"syscall"
1218

1319
"github.com/projectdiscovery/gologger"
1420
"github.com/projectdiscovery/simplehttpserver/pkg/sslcert"
21+
"github.com/projectdiscovery/simplehttpserver/pkg/tcpserver"
1522
)
1623

1724
type options struct {
1825
ListenAddress string
1926
Folder string
20-
Username string
21-
Password string
27+
BasicAuth string
28+
username string
29+
password string
2230
Realm string
2331
Certificate string
2432
Key string
2533
Domain string
2634
HTTPS bool
2735
Verbose bool
2836
Upload bool
37+
TCP bool
38+
RulesFile string
39+
TLS bool
2940
}
3041

3142
var opts options
3243

3344
func main() {
3445
flag.StringVar(&opts.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port")
46+
flag.BoolVar(&opts.TCP, "tcp", false, "TCP Server")
47+
flag.BoolVar(&opts.TLS, "tls", false, "Enable TCP TLS")
48+
flag.StringVar(&opts.RulesFile, "rules", "", "Rules yaml file")
3549
flag.StringVar(&opts.Folder, "path", ".", "Folder")
3650
flag.BoolVar(&opts.Upload, "upload", false, "Enable upload via PUT")
3751
flag.BoolVar(&opts.HTTPS, "https", false, "HTTPS")
3852
flag.StringVar(&opts.Certificate, "cert", "", "Certificate")
3953
flag.StringVar(&opts.Key, "key", "", "Key")
4054
flag.StringVar(&opts.Domain, "domain", "local.host", "Domain")
4155
flag.BoolVar(&opts.Verbose, "v", false, "Verbose")
42-
flag.StringVar(&opts.Username, "username", "", "Basic auth username")
43-
flag.StringVar(&opts.Password, "password", "", "Basic auth password")
56+
flag.StringVar(&opts.BasicAuth, "basic-auth", "", "Basic auth (username:password)")
4457
flag.StringVar(&opts.Realm, "realm", "Please enter username and password", "Realm")
4558

4659
flag.Parse()
@@ -49,15 +62,37 @@ func main() {
4962
opts.Folder = flag.Args()[0]
5063
}
5164

65+
if opts.TCP {
66+
serverTCP, err := tcpserver.New(tcpserver.Options{Listen: opts.ListenAddress, TLS: opts.TLS, Domain: "local.host"})
67+
if err != nil {
68+
gologger.Fatal().Msgf("%s\n", err)
69+
}
70+
err = serverTCP.LoadTemplate(opts.RulesFile)
71+
if err != nil {
72+
gologger.Fatal().Msgf("%s\n", err)
73+
}
74+
75+
gologger.Print().Msgf("%s\n", serverTCP.ListenAndServe())
76+
}
77+
5278
gologger.Print().Msgf("Serving %s on http://%s/...", opts.Folder, opts.ListenAddress)
5379
layers := loglayer(http.FileServer(http.Dir(opts.Folder)))
54-
if opts.Username != "" || opts.Password != "" {
80+
if opts.BasicAuth != "" {
81+
baTokens := strings.SplitN(opts.BasicAuth, ":", 2)
82+
if len(baTokens) > 0 {
83+
opts.username = baTokens[0]
84+
}
85+
if len(baTokens) > 1 {
86+
opts.password = baTokens[1]
87+
}
5588
layers = loglayer(basicauthlayer(http.FileServer(http.Dir(opts.Folder))))
5689
}
57-
5890
if opts.Upload {
59-
gologger.Print().Msgf("Upload enabled")
91+
gologger.Print().Msg("Starting service with Upload enabled")
6092
}
93+
retry_listen:
94+
gologger.Print().Msgf("Serving %s on http://%s/...", opts.Folder, opts.ListenAddress)
95+
var err error
6196
if opts.HTTPS {
6297
if opts.Certificate == "" || opts.Key == "" {
6398
tlsOptions := sslcert.DefaultOptions
@@ -76,7 +111,19 @@ func main() {
76111
gologger.Print().Msgf("%s\n", http.ListenAndServeTLS(opts.ListenAddress, opts.Certificate, opts.Key, layers))
77112
}
78113
} else {
79-
gologger.Print().Msgf("%s\n", http.ListenAndServe(opts.ListenAddress, layers))
114+
err = http.ListenAndServe(opts.ListenAddress, layers)
115+
}
116+
if err != nil {
117+
if isErrorAddressAlreadyInUse(err) {
118+
gologger.Print().Msgf("Can't listen on %s: %s - retrying with another port\n", opts.ListenAddress, err)
119+
newListenAddress, err := incPort(opts.ListenAddress)
120+
if err != nil {
121+
gologger.Fatal().Msgf("%s\n", err)
122+
}
123+
opts.ListenAddress = newListenAddress
124+
goto retry_listen
125+
}
126+
gologger.Print().Msgf("%s\n", err)
80127
}
81128
}
82129

@@ -90,11 +137,11 @@ func loglayer(handler http.Handler) http.Handler {
90137
if opts.Upload && r.Method == http.MethodPut {
91138
data, err := ioutil.ReadAll(r.Body)
92139
if err != nil {
93-
log.Println(err)
140+
gologger.Print().Msgf("%s\n", err)
94141
}
95142
err = handleUpload(path.Base(r.URL.Path), data)
96143
if err != nil {
97-
log.Println(err)
144+
gologger.Print().Msgf("%s\n", err)
98145
}
99146
}
100147

@@ -111,7 +158,7 @@ func loglayer(handler http.Handler) http.Handler {
111158
func basicauthlayer(handler http.Handler) http.HandlerFunc {
112159
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
113160
user, pass, ok := r.BasicAuth()
114-
if !ok || user != opts.Username || pass != opts.Password {
161+
if !ok || user != opts.username || pass != opts.password {
115162
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", opts.Realm))
116163
w.WriteHeader(http.StatusUnauthorized)
117164
w.Write([]byte("Unauthorized.\n")) //nolint
@@ -148,3 +195,39 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
148195
func handleUpload(file string, data []byte) error {
149196
return ioutil.WriteFile(file, data, 0655)
150197
}
198+
199+
func isErrorAddressAlreadyInUse(err error) bool {
200+
var eOsSyscall *os.SyscallError
201+
if !errors.As(err, &eOsSyscall) {
202+
return false
203+
}
204+
var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr)
205+
if !errors.As(eOsSyscall, &errErrno) {
206+
return false
207+
}
208+
if errErrno == syscall.EADDRINUSE {
209+
return true
210+
}
211+
const WSAEADDRINUSE = 10048
212+
if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE {
213+
return true
214+
}
215+
return false
216+
}
217+
218+
func incPort(address string) (string, error) {
219+
addrOrig, portOrig, err := net.SplitHostPort(address)
220+
if err != nil {
221+
return address, err
222+
}
223+
224+
// increment port
225+
portNumber, err := strconv.Atoi(portOrig)
226+
if err != nil {
227+
return address, err
228+
}
229+
portNumber++
230+
newPort := strconv.FormatInt(int64(portNumber), 10)
231+
232+
return net.JoinHostPort(addrOrig, newPort), nil
233+
}

0 commit comments

Comments
 (0)