3 import "database/sql"
\r
6 import "html/template"
\r
11 import _ "github.com/mattn/go-sqlite3"
\r
14 var templates = template.Must(template.ParseFiles("templates/landing.html"))
\r
17 port := flag.Int("port", 6543, "Default port on which to accept requests")
\r
18 url := flag.String("url", "http://localhost:6543/stats/submit", "URL to send POST requests against")
\r
22 if len(flag.Args()) < 1 {
\r
23 fmt.Println("Insufficient arguments: need a <command> to run. Exiting...")
\r
27 command := flag.Args()[0]
\r
29 case command == "drop":
\r
31 case command == "create":
\r
33 case command == "serve":
\r
35 case command == "resubmit":
\r
37 case command == "list":
\r
40 fmt.Println("Unknown command! Exiting...")
\r
45 // override the default Usage function to show the different "commands"
\r
46 // that are in the switch statement in main()
\r
48 fmt.Fprintf(os.Stderr, "Usage of xs_interceptor:\n")
\r
49 fmt.Fprintf(os.Stderr, " xs_interceptor [options] <command>\n\n")
\r
50 fmt.Fprintf(os.Stderr, "Where <command> is one of the following:\n")
\r
51 fmt.Fprintf(os.Stderr, " create - create the requests db (sqlite3 db file)\n")
\r
52 fmt.Fprintf(os.Stderr, " drop - remove the requests db\n")
\r
53 fmt.Fprintf(os.Stderr, " list - lists the requests in the db\n")
\r
54 fmt.Fprintf(os.Stderr, " serve - listens for stats requests, storing them if found\n")
\r
55 fmt.Fprintf(os.Stderr, " resubmit - resubmits the requests to another URL\n\n")
\r
56 fmt.Fprintf(os.Stderr, "Where [options] is one or more of the following:\n")
\r
57 fmt.Fprintf(os.Stderr, " -port - port number (int) to listen on for 'serve' command\n")
\r
58 fmt.Fprintf(os.Stderr, " -url - url (string) to submit requests\n\n")
\r
61 // removes the requests database. it is just a file, so this is really easy.
\r
63 err := os.Remove("middleman.db")
\r
66 fmt.Println("Error dropping the database middleman.db. Exiting...")
\r
69 fmt.Println("Dropped middleman.db successfully!")
\r
74 // creates the sqlite database. it's a hard-coded name because I don't see
\r
75 // a need to change db names for this purpose.
\r
77 db, err := sql.Open("sqlite3", "./middleman.db")
\r
81 fmt.Println("Error creating the database middleman.db. Exiting...")
\r
85 fmt.Println("Created middleman.db successfully!")
\r
89 CREATE TABLE requests (
\r
90 request_id INTEGER PRIMARY KEY ASC,
\r
91 blind_id_header TEXT,
\r
92 ip_addr VARCHAR(32),
\r
99 fmt.Println("Error creating the table 'requests' in middleman.db. Exiting...")
\r
102 fmt.Println("Created table 'requests' successfully!")
\r
106 // an HTTP server that responds to two types of URLs: stats submissions (which it records)
\r
107 // and everything else, which receive a down-page
\r
108 func serve(port int) {
\r
112 http.HandleFunc("/", defaultHandler)
\r
113 http.HandleFunc("/stats/submit", makeSubmitHandler(requests))
\r
114 http.Handle("/m/", http.StripPrefix("/m/", http.FileServer(http.Dir("m"))))
\r
117 fmt.Printf("Serving on port %d...\n", port)
\r
118 addr := fmt.Sprintf(":%d", port)
\r
120 http.ListenAndServe(addr, nil)
\r
121 time.Sleep(100*time.Millisecond)
\r
125 // intercepts all URLs, displays a landing page
\r
126 func defaultHandler(w http.ResponseWriter, r *http.Request) {
\r
127 err := templates.ExecuteTemplate(w, "landing.html", nil)
\r
129 http.Error(w, err.Error(), http.StatusInternalServerError)
\r
133 // accepts stats requests at a given URL, stores them in requests
\r
134 func makeSubmitHandler(requests int) http.HandlerFunc {
\r
135 return func(w http.ResponseWriter, r *http.Request) {
\r
136 fmt.Println("in submission handler")
\r
138 if r.Method != "POST" {
\r
139 http.Redirect(w, r, "/", http.StatusFound)
\r
143 // check for blind ID header. If we don't have it, don't do anything
\r
144 var blind_id_header string
\r
145 _, ok := r.Header["X-D0-Blind-Id-Detached-Signature"]
\r
147 fmt.Println("Found a blind_id header. Extracting...")
\r
148 blind_id_header = r.Header["X-D0-Blind-Id-Detached-Signature"][0]
\r
150 fmt.Println("No blind_id header found.")
\r
151 blind_id_header = ""
\r
154 remoteAddr := getRemoteAddr(r)
\r
156 // and finally, read the body
\r
157 body := make([]byte, r.ContentLength)
\r
163 _, err := db.Exec("INSERT INTO requests(blind_id_header, ip_addr, body, bodylength) VALUES(?, ?, ?, ?)", blind_id_header, remoteAddr, string(body), r.ContentLength)
\r
165 fmt.Println("Unable to insert request.")
\r
171 // gets the remote address out of http.Requests with X-Forwarded-For handling
\r
172 func getRemoteAddr(r *http.Request) (remoteAddr string) {
\r
173 val, ok := r.Header["X-Forwarded-For"]
\r
175 remoteAddr = val[0]
\r
177 remoteAddr = r.RemoteAddr
\r
180 // sometimes a ":<port number>" comes attached, which
\r
182 idx := strings.Index(remoteAddr, ":")
\r
184 remoteAddr = remoteAddr[0:idx]
\r
190 // resubmits stats request to a particular URL. this is intended to be used when
\r
191 // you want to write back to the "real" XonStat
\r
192 func resubmit(url string) {
\r
196 rows, err := db.Query("SELECT request_id, ip_addr, blind_id_header, body, bodylength FROM requests ORDER BY request_id")
\r
198 fmt.Println("Error reading rows from the database. Exiting...")
\r
203 successfulRequests := make([]int, 0, 10)
\r
205 // could use a struct here, but isntead just a bunch of vars
\r
207 var blind_id_header string
\r
212 if err := rows.Scan(&request_id, &ip_addr, &blind_id_header, &body, &bodylength); err != nil {
\r
213 fmt.Println("Error reading row for submission. Continuing...")
\r
217 req, _ := http.NewRequest("POST", url, strings.NewReader(body))
\r
218 //req.ContentLength = int64(bodylength)
\r
219 //req.ContentLength = 0
\r
220 req.ContentLength = int64(len([]byte(body)))
\r
222 header := map[string][]string{
\r
223 "X-D0-Blind-Id-Detached-Signature": {blind_id_header},
\r
224 "X-Forwarded-For": {ip_addr},
\r
226 req.Header = header
\r
228 res, err := http.DefaultClient.Do(req)
\r
230 fmt.Printf("Error submitting request #%d. Continuing...\n", request_id)
\r
234 defer res.Body.Close()
\r
236 fmt.Printf("Request #%d: %s\n", request_id, res.Status)
\r
238 // undeliverables requests will still live in the database,
\r
239 // but we can clear out the 200 ones for sure
\r
240 if res.StatusCode == 200 {
\r
241 successfulRequests = append(successfulRequests, request_id)
\r
245 // now that we're done resubmitting, let's clean up the successful requests
\r
246 // by deleting them outright from the database
\r
247 for _, val := range successfulRequests {
\r
248 deleteRequest(db, val)
\r
252 // lists all the requests and their information *in the XonStat log format* in
\r
253 // order to 1) show what's in the db and 2) to be able to save/parse it (with
\r
254 // xs_parse) for later use.
\r
259 rows, err := db.Query("SELECT request_id, ip_addr, blind_id_header, body FROM requests ORDER BY request_id")
\r
261 fmt.Println("Error reading rows from the database. Exiting...")
\r
268 var blind_id_header string
\r
272 if err := rows.Scan(&request_id, &ip_addr, &blind_id_header, &body); err != nil {
\r
273 fmt.Println("Error opening middleman.db. Did you create it?")
\r
277 fmt.Printf("Request: %d\n", request_id)
\r
278 fmt.Printf("IP Address: %s\n", ip_addr)
\r
279 fmt.Println("----- BEGIN REQUEST BODY -----")
\r
281 if len(blind_id_header) > 0 {
\r
282 fmt.Printf("d0_blind_id: %s\n", blind_id_header)
\r
286 fmt.Printf("\n----- END REQUEST BODY -----\n")
\r
290 // hard-coded sqlite database connection retriever to keep it simple
\r
291 func getDBConn() *sql.DB {
\r
292 conn, err := sql.Open("sqlite3", "./middleman.db")
\r
295 fmt.Println("Error opening middleman.db. Did you create it?")
\r
302 // removes reqeusts from the database by request_id
\r
303 func deleteRequest(db *sql.DB, request_id int) {
\r
304 _, err := db.Exec("delete from requests where request_id = ?", request_id)
\r
306 fmt.Printf("Could not remove request_id %d from the database. Reason: %v\n", request_id, err)
\r
308 fmt.Printf("Request #%d removed from the database.\n", request_id)
\r