package api import ( "bytes" "crypto/tls" "encoding/json" "errors" "fmt" "net/http" "strings" "github.com/PuerkitoBio/goquery" "github.com/gophish/gophish/dialer" log "github.com/gophish/gophish/logger" "github.com/gophish/gophish/models" "github.com/gophish/gophish/util" "github.com/jordan-wright/email" ) type cloneRequest struct { URL string `json:"url"` IncludeResources bool `json:"include_resources"` } func (cr *cloneRequest) validate() error { if cr.URL == "" { return errors.New("No URL Specified") } return nil } type cloneResponse struct { HTML string `json:"html"` } type emailResponse struct { Text string `json:"text"` HTML string `json:"html"` Subject string `json:"subject"` } // ImportGroup imports a CSV of group members func (as *Server) ImportGroup(w http.ResponseWriter, r *http.Request) { ts, err := util.ParseCSV(r) if err != nil { JSONResponse(w, models.Response{Success: false, Message: "Error parsing CSV"}, http.StatusInternalServerError) return } JSONResponse(w, ts, http.StatusOK) } // ImportEmail allows for the importing of email. // Returns a Message object func (as *Server) ImportEmail(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest) return } ir := struct { Content string `json:"content"` ConvertLinks bool `json:"convert_links"` }{} err := json.NewDecoder(r.Body).Decode(&ir) if err != nil { JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest) return } e, err := email.NewEmailFromReader(strings.NewReader(ir.Content)) if err != nil { log.Error(err) } // If the user wants to convert links to point to // the landing page, let's make it happen by changing up // e.HTML if ir.ConvertLinks { d, err := goquery.NewDocumentFromReader(bytes.NewReader(e.HTML)) if err != nil { JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest) return } d.Find("a").Each(func(i int, a *goquery.Selection) { a.SetAttr("href", "{{.URL}}") }) h, err := d.Html() if err != nil { JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError) return } e.HTML = []byte(h) } er := emailResponse{ Subject: e.Subject, Text: string(e.Text), HTML: string(e.HTML), } JSONResponse(w, er, http.StatusOK) } // ImportSite allows for the importing of HTML from a website // Without "include_resources" set, it will merely place a "base" tag // so that all resources can be loaded relative to the given URL. func (as *Server) ImportSite(w http.ResponseWriter, r *http.Request) { cr := cloneRequest{} if r.Method != "POST" { JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest) return } err := json.NewDecoder(r.Body).Decode(&cr) if err != nil { JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest) return } if err = cr.validate(); err != nil { JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest) return } restrictedDialer := dialer.Dialer() tr := &http.Transport{ DialContext: restrictedDialer.DialContext, TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } client := &http.Client{Transport: tr} resp, err := client.Get(cr.URL) if err != nil { JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest) return } // Insert the base href tag to better handle relative resources d, err := goquery.NewDocumentFromResponse(resp) if err != nil { JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest) return } // Assuming we don't want to include resources, we'll need a base href if d.Find("head base").Length() == 0 { d.Find("head").PrependHtml(fmt.Sprintf("", cr.URL)) } forms := d.Find("form") forms.Each(func(i int, f *goquery.Selection) { // We'll want to store where we got the form from // (the current URL) url := f.AttrOr("action", cr.URL) if !strings.HasPrefix(url, "http") { url = fmt.Sprintf("%s%s", cr.URL, url) } f.PrependHtml(fmt.Sprintf("", url)) }) h, err := d.Html() if err != nil { JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError) return } cs := cloneResponse{HTML: h} JSONResponse(w, cs, http.StatusOK) }