mirror of https://github.com/gophish/gophish
155 lines
4.4 KiB
Go
155 lines
4.4 KiB
Go
|
package api
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/tls"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/PuerkitoBio/goquery"
|
||
|
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
|
||
|
}
|
||
|
tr := &http.Transport{
|
||
|
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("<base href=\"%s\">", 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("<input type=\"hidden\" name=\"__original_url\" value=\"%s\"/>", 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)
|
||
|
}
|