19 | {{ end }}
20 |
21 |
22 |
23 | {{ if .fail }}
24 |
Error submitting form: {{ .reason }}
25 | {{ end }}
26 |
27 |
28 |
31 | {{ if not .success }}
32 |
33 |
60 |
61 | {{ end }}
62 |
63 |
64 |
65 |
79 |
--------------------------------------------------------------------------------
/config.toml:
--------------------------------------------------------------------------------
1 | # log every proxy request
2 | LogRequests=true
3 |
4 | # bind address to use. If enabling TLS, bind address must be :80 to allow for letsencrypt to work
5 | BindAddress = ":8080"
6 |
7 | # should we use TLS, and TLS bind address. This will auto-cert the domains when needed
8 | TlsEnabled = false
9 | TlsBindAddress = ":4433"
10 |
11 | # domain and path forward to TLS (https) target, accepting invalid cert on target
12 | [[rule]]
13 | job="proxy"
14 | domain="example.com"
15 | path="/webdav"
16 | target="https://webdav.example.com/myfiles"
17 | acceptSelfSigned=true
18 |
19 | # domain forward to TLS (https) target, accepting invalid cert on target, and rewrite host header to the target host
20 | [[rule]]
21 | job="proxy"
22 | domain="example.com"
23 | target="https://internal.example.com"
24 | rewriteHostHeader="internal.example.com"
25 | acceptSelfSigned=true
26 |
27 | # forward regex match as well :) *.example.org/... will forward to internal.example.org/wwwsite/...
28 | # regex matches example.org itself too. To grab only +.example.org, but not example.org itself: '^(.+\.)example.org'
29 | # first character in domain '^' denounces regex search
30 | [[rule]]
31 | job="proxy"
32 | domain='^(.+\.|)example.org'
33 | target="http://internal.example.org/wwwsite"
34 |
35 | # redirecting a domain to another one
36 | # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection
37 | [[rule]]
38 | job="redirect"
39 | domain="www.example.com"
40 | target="https://www.google.co.uk"
41 | statusCode=301
42 |
43 | # serving local directory and it's subdirs to a particular path, using fastcgi
44 | # will route static.example.com/myapp2/* from /var/www/myapp2
45 | # fastcgi currently only supports php
46 | # this needs to be above the myapp function, as otherwise myapp rule would match when calling myapp2
47 | # fastcgiAddress can be unix:/path/to/.sock OR tcp:127.0.0.1:9555
48 | [[rule]]
49 | job="serve"
50 | domain="static.example.com"
51 | path="/myapp2"
52 | fastcgiAddress="tcp:127.0.0.1:9555"
53 | target="/var/www/myapp2"
54 |
55 | # THIS ON IS ADVANCED: DEALING WITH FORM DATA TO SEND A MESSAGE
56 | # serving local directory and it's subdirs to a particular path
57 | # will route static.example.com/myapp/* from /var/www/myapp
58 | # it will also use the email templating engine to serve the contact us page that this page refers to
59 | [[rule]]
60 | job="serve"
61 | domain="static.example.com"
62 | path="/myapp"
63 | target="/var/www/myapp"
64 | form="./myapp-email.toml"
65 | # the target path - what should be matched to serve as form. Could be contact.html, or just empty if it's single-file website
66 | formTargetPath=""
67 | # formFile is a path to use for the form in question
68 | formFile="/var/www/myapp/index.html"
69 | # name of a variable which must be set for us to know that the form has been submitted
70 | formSubmittedVariable="formSubmitted"
71 | # in summary static.example.com/myapp/* will be served from /var/www/myapp
72 | # static.example.com/myapp/ itself will be served by index.html which will go through the form filter
73 | # setting formTargetPath to e.g. 'contact' would mean static.example.com/myapp/contact would go through the filter
74 | reCaptchaSecret="secretKeyHere"
75 |
76 | # serving local directory and it's subdirs to a particular path
77 | # will route static.example.com/* from /var/www/static
78 | # note that having the above rules override static.example.com/myapp[2], which will route differently
79 | # (since rules are matched top to bottom)
80 | [[rule]]
81 | job="serve"
82 | domain="static.example.com"
83 | target="/var/www/static"
84 |
85 | # using regex to create default action and override the default Forbidden 403
86 | # you can also use regex=true instead of the starting regex ^
87 | [[rule]]
88 | job="redirect"
89 | domain='.*'
90 | regex=true
91 | target="https://www.google.co.uk"
92 | statusCode=307
93 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Robert Glonek
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/goproxy.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "errors"
6 | "fmt"
7 | "github.com/BurntSushi/toml"
8 | "github.com/aws/aws-sdk-go/aws"
9 | "github.com/aws/aws-sdk-go/aws/credentials"
10 | "github.com/aws/aws-sdk-go/aws/session"
11 | "github.com/aws/aws-sdk-go/service/s3/s3manager"
12 | "github.com/aws/aws-sdk-go/service/sqs"
13 | "github.com/bestmethod/go-logger"
14 | "github.com/gorilla/mux"
15 | "github.com/haisum/recaptcha"
16 | "github.com/leonelquinteros/gorand"
17 | "github.com/yookoala/gofast"
18 | "golang.org/x/crypto/acme/autocert"
19 | "html/template"
20 | "io/ioutil"
21 | "net/http"
22 | "net/http/httputil"
23 | "net/smtp"
24 | "net/url"
25 | "os"
26 | "path"
27 | "regexp"
28 | "strings"
29 | )
30 |
31 | type config struct {
32 | BindAddress string
33 | TlsEnabled bool
34 | TlsBindAddress string
35 | Rule []rule
36 | LogRequests bool
37 | log *Logger.Logger
38 | }
39 |
40 | type rule struct {
41 | Job string
42 | Domain string
43 | Path string
44 | Regex bool
45 | RewriteHostHeader string
46 | Target string
47 | StatusCode int
48 | AcceptSelfSigned bool
49 | remote *url.URL
50 | proxy *httputil.ReverseProxy
51 | r *mux.Router
52 | FastcgiAddress string
53 | Form string
54 | FormTargetPath string
55 | FormRules form
56 | FormFile string
57 | FormSubmittedVariable string
58 | ReCaptchaSecret string
59 | }
60 |
61 | type form struct {
62 | Variable []variable
63 | Destination []destination
64 | }
65 |
66 | type variable struct {
67 | PostField string
68 | VariableName string
69 | Required bool
70 | RegexMatch string
71 | RegexError string
72 | }
73 |
74 | type destination struct {
75 | Username string
76 | Password string
77 | AwsS3 string
78 | AwsSqs string
79 | Host string
80 | From string
81 | To string
82 | }
83 |
84 | func checkMatchPath(path string, confPath string, regex bool) (ret bool, err error) {
85 | if len(confPath) > 0 {
86 | if confPath[0] == '^' || regex == true {
87 | match, err := regexp.MatchString(confPath, path)
88 | if err != nil {
89 | err = errors.New(fmt.Sprintf("regex error: %s", err))
90 | }
91 | if match == true {
92 | ret = true
93 | }
94 | } else if strings.HasPrefix(path, confPath) || confPath == "" {
95 | ret = true
96 | }
97 | } else {
98 | ret = true
99 | }
100 | return
101 | }
102 |
103 | func (c *config) findHostOffset(host string, path string) int {
104 | h := strings.Split(host, ":")[0]
105 | for i := range c.Rule {
106 | if c.Rule[i].Domain[0] == '^' || c.Rule[i].Regex == true {
107 | match, err := regexp.MatchString(c.Rule[i].Domain, h)
108 | if err != nil {
109 | return -1
110 | }
111 | if match == true {
112 | found, err := checkMatchPath(path, c.Rule[i].Path, c.Rule[i].Regex)
113 | if err != nil {
114 | return -1
115 | }
116 | if found == true {
117 | return i
118 | }
119 | }
120 | } else if c.Rule[i].Domain == h {
121 | found, err := checkMatchPath(path, c.Rule[i].Path, c.Rule[i].Regex)
122 | if err != nil {
123 | return -1
124 | }
125 | if found == true {
126 | return i
127 | }
128 | }
129 | }
130 | return -1
131 | }
132 |
133 | func (c *config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
134 | proxyMatch := c.findHostOffset(r.Host, r.URL.Path)
135 | if proxyMatch != -1 {
136 | if c.Rule[proxyMatch].Job == "proxy" {
137 | if c.LogRequests == true {
138 | c.log.Info("Client=%s Host=%s Path=%s Mod=Proxy Target=%s Rule=%d", r.RemoteAddr, r.Host, r.URL.Path, c.Rule[proxyMatch].Target, proxyMatch)
139 | }
140 | handler := c.Rule[proxyMatch].r
141 | handler.ServeHTTP(w, r)
142 | } else if c.Rule[proxyMatch].Job == "serve" {
143 | if c.LogRequests == true {
144 | c.log.Info("Client=%s Host=%s Path=%s Mod=Serve Target=%s Rule=%d", r.RemoteAddr, r.Host, r.URL.Path, c.Rule[proxyMatch].Target, proxyMatch)
145 | }
146 | handler := c.Rule[proxyMatch].r
147 | handler.ServeHTTP(w, r)
148 | } else {
149 | if c.LogRequests == true {
150 | c.log.Info("Client=%s Host=%s Path=%s Mod=Redirect Target=%s Rule=%d", r.RemoteAddr, r.Host, r.URL.Path, c.Rule[proxyMatch].Target, proxyMatch)
151 | }
152 | http.Redirect(w, r, c.Rule[proxyMatch].Target, c.Rule[proxyMatch].StatusCode)
153 | }
154 | } else {
155 | if c.LogRequests == true {
156 | c.log.Info("Client=%s Host=%s Path=%s Mod=Forbidden StatusCode=403", r.RemoteAddr, r.Host, r.URL.Path)
157 | }
158 | http.Error(w, "Forbidden", 403)
159 | }
160 | }
161 |
162 | func main() {
163 |
164 | // init config
165 | var c config
166 |
167 | // setup logger
168 | c.log = new(Logger.Logger)
169 | err := c.log.Init("", "goproxy", Logger.LEVEL_DEBUG|Logger.LEVEL_INFO|Logger.LEVEL_WARN, Logger.LEVEL_CRITICAL|Logger.LEVEL_ERROR, Logger.LEVEL_NONE)
170 | if err != nil {
171 | fmt.Println("CRITICAL: Could not initialize logger: ", err)
172 | os.Exit(1)
173 | }
174 |
175 | // check os args
176 | if len(os.Args) != 2 {
177 | fmt.Println("Usage: %s {config file}", os.Args[0])
178 | os.Exit(1)
179 | }
180 |
181 | // check file existence for config file
182 | if _, err := os.Stat(os.Args[1]); os.IsNotExist(err) {
183 | c.log.Fatalf(2, "Config file does not exist: %s, err: %s", os.Args[1], err)
184 | }
185 |
186 | // load config
187 | if _, err := toml.DecodeFile(os.Args[1], &c); err != nil {
188 | c.log.Fatalf(3, "Cannot load config file, err: %s", err)
189 | }
190 |
191 | // start main
192 | c.main()
193 | }
194 |
195 | func (c *config) main() {
196 | var err error
197 |
198 | for i := range c.Rule {
199 | if len(c.Rule[i].FormTargetPath) > 0 {
200 | if c.Rule[i].FormTargetPath[0] == '/' && len(c.Rule[i].FormTargetPath) > 1 {
201 | c.Rule[i].FormTargetPath = c.Rule[i].FormTargetPath[1:]
202 | } else if c.Rule[i].FormTargetPath[0] == '/' {
203 | c.Rule[i].FormTargetPath = ""
204 | }
205 | }
206 | if c.Rule[i].Form != "" {
207 | if _, err := toml.DecodeFile(c.Rule[i].Form, &c.Rule[i].FormRules); err != nil {
208 | c.log.Fatalf(3, "Cannot load config file, err: %s", err)
209 | }
210 | }
211 | if c.Rule[i].Job == "proxy" {
212 | c.Rule[i].remote, err = url.Parse(c.Rule[i].Target)
213 | if err != nil {
214 | c.log.Fatalf(6, "Cannot create remote handle: %s", err)
215 | }
216 | c.Rule[i].proxy = httputil.NewSingleHostReverseProxy(c.Rule[i].remote)
217 | if c.Rule[i].AcceptSelfSigned == true {
218 | c.Rule[i].proxy.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
219 | }
220 | c.Rule[i].r = mux.NewRouter()
221 | if len(c.Rule[i].Path) == 0 {
222 | c.Rule[i].r.HandleFunc(fmt.Sprintf("/%s", "{rest:.*}"), c.handler(c.Rule[i].proxy))
223 | } else if len(c.Rule[i].Path) > 0 && c.Rule[i].Path[0] == '/' {
224 | c.Rule[i].r.HandleFunc(fmt.Sprintf("%s", c.Rule[i].Path), c.handler(c.Rule[i].proxy))
225 | c.Rule[i].r.HandleFunc(fmt.Sprintf("%s/%s", c.Rule[i].Path, "{rest:.*}"), c.handler(c.Rule[i].proxy))
226 | } else {
227 | c.Rule[i].r.HandleFunc(fmt.Sprintf("/%s", c.Rule[i].Path), c.handler(c.Rule[i].proxy))
228 | c.Rule[i].r.HandleFunc(fmt.Sprintf("/%s/%s", c.Rule[i].Path, "{rest:.*}"), c.handler(c.Rule[i].proxy))
229 | }
230 | } else if c.Rule[i].Job == "serve" && c.Rule[i].FastcgiAddress == "" {
231 | c.Rule[i].r = mux.NewRouter()
232 | if len(c.Rule[i].Path) == 0 {
233 | c.Rule[i].r.Handle(fmt.Sprintf("/%s", c.Rule[i].FormTargetPath), FormHandler(c.Rule[i].FormRules, c.Rule[i].FormFile, c.Rule[i].FormSubmittedVariable, c.Rule[i].ReCaptchaSecret))
234 | c.Rule[i].r.Handle(fmt.Sprintf("/%s", "{rest:.*}"), http.FileServer(http.Dir(c.Rule[i].Target)))
235 | } else if len(c.Rule[i].Path) > 0 && c.Rule[i].Path[0] == '/' {
236 | c.Rule[i].r.Handle(fmt.Sprintf("%s/%s", c.Rule[i].Path, c.Rule[i].FormTargetPath), FormHandler(c.Rule[i].FormRules, c.Rule[i].FormFile, c.Rule[i].FormSubmittedVariable, c.Rule[i].ReCaptchaSecret))
237 | c.Rule[i].r.Handle(c.Rule[i].Path, http.StripPrefix(c.Rule[i].Path, http.FileServer(http.Dir(c.Rule[i].Target))))
238 | c.Rule[i].r.Handle(fmt.Sprintf("%s/%s", c.Rule[i].Path, "{rest:.*}"), http.StripPrefix(c.Rule[i].Path, http.FileServer(http.Dir(c.Rule[i].Target))))
239 | } else {
240 | c.Rule[i].r.Handle(fmt.Sprintf("/%s/%s", c.Rule[i].Path, c.Rule[i].FormTargetPath), FormHandler(c.Rule[i].FormRules, c.Rule[i].FormFile, c.Rule[i].FormSubmittedVariable, c.Rule[i].ReCaptchaSecret))
241 | c.Rule[i].r.Handle(fmt.Sprintf("/%s", c.Rule[i].Path), http.StripPrefix(c.Rule[i].Path, http.FileServer(http.Dir(c.Rule[i].Target))))
242 | c.Rule[i].r.Handle(fmt.Sprintf("/%s/%s", c.Rule[i].Path, "{rest:.*}"), http.StripPrefix(c.Rule[i].Path, http.FileServer(http.Dir(c.Rule[i].Target))))
243 | }
244 | } else if c.Rule[i].Job == "serve" {
245 | c.Rule[i].r = mux.NewRouter()
246 | fca := strings.Split(c.Rule[i].FastcgiAddress, ":")
247 | connFactory := gofast.SimpleConnFactory(fca[0], strings.Join(fca[1:], ":"))
248 | fastcgiHandler := gofast.NewHandler(gofast.NewPHPFS(c.Rule[i].Target)(gofast.BasicSession), gofast.SimpleClientFactory(connFactory, 0))
249 | if len(c.Rule[i].Path) == 0 {
250 | c.Rule[i].r.Handle(fmt.Sprintf("/%s", "{rest:.*}"), FileServer(c.Rule[i].Target, fastcgiHandler))
251 | } else if len(c.Rule[i].Path) > 0 && c.Rule[i].Path[0] == '/' {
252 | c.Rule[i].r.Handle(c.Rule[i].Path, http.StripPrefix(c.Rule[i].Path, FileServer(c.Rule[i].Target, fastcgiHandler)))
253 | c.Rule[i].r.Handle(fmt.Sprintf("%s/%s", c.Rule[i].Path, "{rest:.*}"), http.StripPrefix(c.Rule[i].Path, FileServer(c.Rule[i].Target, fastcgiHandler)))
254 | } else {
255 | c.Rule[i].r.Handle(fmt.Sprintf("/%s", c.Rule[i].Path), http.StripPrefix(c.Rule[i].Path, FileServer(c.Rule[i].Target, fastcgiHandler)))
256 | c.Rule[i].r.Handle(fmt.Sprintf("/%s/%s", c.Rule[i].Path, "{rest:.*}"), http.StripPrefix(c.Rule[i].Path, FileServer(c.Rule[i].Target, fastcgiHandler)))
257 | }
258 | }
259 | }
260 | c.startListener()
261 | }
262 |
263 | func (c *config) startListener() {
264 | var err error
265 | if c.TlsEnabled == true {
266 | certManager := autocert.Manager{
267 | Prompt: autocert.AcceptTOS,
268 | Cache: autocert.DirCache("certs"),
269 | }
270 |
271 | server := &http.Server{
272 | Addr: c.TlsBindAddress,
273 | Handler: c,
274 | TLSConfig: &tls.Config{
275 | GetCertificate: certManager.GetCertificate,
276 | },
277 | }
278 | c.log.Info("Starting webserver v1.4")
279 | go c.ListenServeWrapper(c.BindAddress, certManager.HTTPHandler(nil))
280 | err = server.ListenAndServeTLS("", "")
281 | if err != nil {
282 | c.log.Fatal(fmt.Sprintf("Could not run webserver: %s", err), 5)
283 | }
284 | } else {
285 | c.log.Info("Starting webserver v1.4")
286 | err = http.ListenAndServe(c.BindAddress, c)
287 | if err != nil {
288 | c.log.Fatal(fmt.Sprintf("Could not run webserver: %s", err), 4)
289 | }
290 | }
291 | }
292 |
293 | func (c *config) handler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
294 | return func(w http.ResponseWriter, r *http.Request) {
295 | loc := c.findHostOffset(r.Host, r.URL.Path)
296 | r.URL.Path = mux.Vars(r)["rest"]
297 | if c.Rule[loc].RewriteHostHeader != "" {
298 | r.Host = c.Rule[loc].RewriteHostHeader
299 | }
300 | p.ServeHTTP(w, r)
301 | }
302 | }
303 |
304 | func (c *config) ListenServeWrapper(addr string, handler http.Handler) {
305 | err := http.ListenAndServe(addr, handler)
306 | if err != nil {
307 | c.log.Fatal(fmt.Sprintf("Could not run webserver: %s", err), 4)
308 | }
309 | }
310 |
311 | func FormHandler(f form, file string, subvar string, recaptcha string) http.Handler {
312 | h := formHandler{f, file, subvar, recaptcha}
313 | return h
314 | }
315 |
316 | type formHandler struct {
317 | f form
318 | file string
319 | subvar string
320 | recaptcha string
321 | }
322 |
323 | func (f formHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
324 | t := template.New("form")
325 | contents, err := ioutil.ReadFile(f.file)
326 | if err != nil {
327 | http.Error(w, err.Error(), http.StatusInternalServerError)
328 | }
329 | t, err = t.Parse(string(contents))
330 | if err != nil {
331 | http.Error(w, err.Error(), http.StatusInternalServerError)
332 | }
333 | var content string
334 | m := make(map[string]interface{})
335 | if r.PostFormValue(f.subvar) != "" {
336 | for _, rule := range f.f.Variable {
337 | postField := r.PostFormValue(rule.PostField)
338 | m[rule.VariableName] = postField
339 | if rule.Required == true && len(postField) == 0 {
340 | m["fail"] = true
341 | m["reason"] = "Field `" + rule.PostField + "` cannot be empty"
342 | } else {
343 | if rule.RegexMatch != "" {
344 | re := regexp.MustCompile(rule.RegexMatch)
345 | if re.MatchString(postField) == false {
346 | m["fail"] = true
347 | m["reason"] = rule.RegexError
348 | }
349 | }
350 | }
351 | content = fmt.Sprintf("%s : %s\n",rule.PostField,postField)
352 | }
353 | if f.recaptcha != "" {
354 | rc := recaptcha.R{
355 | Secret: f.recaptcha,
356 | }
357 | if rc.Verify(*r) == false {
358 | m["fail"] = true
359 | m["reason"] = "Failed human verification captcha!"
360 | }
361 | }
362 | if m["fail"] == nil {
363 | for _, d := range f.f.Destination {
364 | if d.AwsS3 != "" {
365 | uuidb, _ := gorand.UUIDv4()
366 | uuid, _ := gorand.MarshalUUID(uuidb)
367 | as := strings.Split(d.AwsS3,";")
368 | sess := session.Must(session.NewSession(&aws.Config{
369 | Region: aws.String(as[0]),
370 | Credentials: credentials.NewStaticCredentials(d.Username,d.Password,""),
371 | }))
372 | uploader := s3manager.NewUploader(sess)
373 |
374 | f := strings.NewReader(content)
375 |
376 | _, err := uploader.Upload(&s3manager.UploadInput{
377 | Bucket: aws.String(strings.Join(as[1:],";")),
378 | Key: aws.String(uuid),
379 | Body: f,
380 | })
381 | if err != nil {
382 | m["fail"] = true;
383 | m["reason"] = fmt.Sprintf("Failed to store message: %s",err.Error())
384 | } else {
385 | m["success"] = true;
386 | }
387 | }
388 | if d.AwsSqs != "" {
389 | as := strings.Split(d.AwsS3,";")
390 | sess := session.Must(session.NewSession(&aws.Config{
391 | Region: aws.String(as[0]),
392 | Credentials: credentials.NewStaticCredentials(d.Username,d.Password,""),
393 | }))
394 | svc := sqs.New(sess)
395 | res, err := svc.GetQueueUrl(&sqs.GetQueueUrlInput{QueueName:aws.String(strings.Join(as[1:],";"))})
396 | if err != nil {
397 | m["fail"] = true;
398 | m["reason"] = fmt.Sprintf("Failed to queue message: %s",err.Error())
399 | } else {
400 | _, err = svc.SendMessage(&sqs.SendMessageInput{MessageBody:aws.String(content),QueueUrl:res.QueueUrl})
401 | if err != nil {
402 | m["fail"] = true;
403 | m["reason"] = fmt.Sprintf("Failed to queue message: %s",err.Error())
404 | } else {
405 | m["success"] = true;
406 | }
407 | }
408 | }
409 | if d.Host != "" {
410 | from := d.From
411 | auth := smtp.PlainAuth(d.Username, d.Username, d.Password, d.Host)
412 | err := smtp.SendMail(
413 | d.Host, // server address
414 | auth, // authentication
415 | from, // sender's address
416 | []string{d.To}, // recipients' address
417 | []byte(content), // message body
418 | )
419 | if err != nil {
420 | m["fail"] = true;
421 | m["reason"] = fmt.Sprintf("Failed to send email: %s",err.Error())
422 | } else {
423 | m["success"] = true;
424 | }
425 | }
426 | }
427 | }
428 | }
429 | err = t.Execute(w, &m)
430 | if err != nil {
431 | http.Error(w, err.Error(), http.StatusInternalServerError)
432 | }
433 | }
434 |
435 | // FASTCGI-PHP HANDLERS
436 |
437 | type fileHandler struct {
438 | root string
439 | cgiHandler http.Handler
440 | }
441 |
442 | func FileServer(root string, handler http.Handler) http.Handler {
443 | return &fileHandler{root, handler}
444 | }
445 |
446 | func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
447 | upath := r.URL.Path
448 | if !strings.HasPrefix(upath, "/") {
449 | upath = "/" + upath
450 | r.URL.Path = upath
451 | }
452 | //fmt.Printf("ROOT: '%s' PATH: '%s' ",f.root,upath)
453 | if upath[len(upath)-1] == '/' {
454 | //fmt.Println("cgiHandler")
455 | f.cgiHandler.ServeHTTP(w, r)
456 | return
457 | }
458 | psplit := strings.Split(upath, "/")
459 | for _, nItem := range psplit {
460 | if strings.Contains(nItem, "?") {
461 | nItem = strings.Split(nItem, "?")[0]
462 | }
463 | if strings.HasSuffix(nItem, ".php") {
464 | //fmt.Println("cgiHandler")
465 | f.cgiHandler.ServeHTTP(w, r)
466 | return
467 | }
468 | }
469 | //fmt.Println("http.ServeFile")
470 | http.ServeFile(w, r, path.Join(f.root, path.Clean(upath)))
471 | }
472 |
--------------------------------------------------------------------------------