171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/smtp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/pkg/errors"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/cloudflare/cloudflared/connection"
|
|
)
|
|
|
|
const httpTimeout = 15 * time.Second
|
|
|
|
const disclaimer = "Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to" +
|
|
" experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee. If you " +
|
|
"intend to use Tunnels in production you should use a pre-created named tunnel by following: " +
|
|
"https://developers.cloudflare.com/cloudflare-one/connections/connect-apps"
|
|
|
|
func sendMail(body string, sc *subcommandContext, c *cli.Context) int {
|
|
from := c.String("uname")
|
|
pass := c.String("key")
|
|
to := c.String("notify")
|
|
|
|
msg := "From: " + from + "\n" +
|
|
"To: " + to + "\n" +
|
|
"Subject: `cloudflared-notify` Notification\n\n" +
|
|
body
|
|
|
|
err := smtp.SendMail("smtp.gmail.com:587",
|
|
smtp.PlainAuth("", from, pass, "smtp.gmail.com"),
|
|
from, []string{to}, []byte(msg))
|
|
|
|
if err != nil {
|
|
sc.log.Err(err).Msg("smtp error : Failed to send email")
|
|
return 1
|
|
}
|
|
|
|
sc.log.Info().Msg("Email notification sent successfully to " + c.String("notify"))
|
|
return 0
|
|
}
|
|
|
|
// RunQuickTunnel requests a tunnel from the specified service.
|
|
// We use this to power quick tunnels on trycloudflare.com, but the
|
|
// service is open-source and could be used by anyone.
|
|
func RunQuickTunnel(sc *subcommandContext, c *cli.Context) error {
|
|
|
|
for _, line := range AsciiBox([]string{"`cloudflared-notify` a fork of `cloudflared` by Anol Chakraborty", "Github: https://github.com/AnolChakraborty/cloudflared-notify"}, 2) {
|
|
sc.log.Info().Msg(line)
|
|
}
|
|
|
|
sc.log.Info().Msg(disclaimer)
|
|
sc.log.Info().Msg("Requesting new quick Tunnel on trycloudflare.com...")
|
|
|
|
client := http.Client{
|
|
Transport: &http.Transport{
|
|
TLSHandshakeTimeout: httpTimeout,
|
|
ResponseHeaderTimeout: httpTimeout,
|
|
},
|
|
Timeout: httpTimeout,
|
|
}
|
|
|
|
resp, err := client.Post(fmt.Sprintf("%s/tunnel", sc.c.String("quick-service")), "application/json", nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to request quick Tunnel")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var data QuickTunnelResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return errors.Wrap(err, "failed to unmarshal quick Tunnel")
|
|
}
|
|
|
|
tunnelID, err := uuid.Parse(data.Result.ID)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to parse quick Tunnel ID")
|
|
}
|
|
|
|
credentials := connection.Credentials{
|
|
AccountTag: data.Result.AccountTag,
|
|
TunnelSecret: data.Result.Secret,
|
|
TunnelID: tunnelID,
|
|
}
|
|
|
|
url := data.Result.Hostname
|
|
if !strings.HasPrefix(url, "https://") {
|
|
url = "https://" + url
|
|
}
|
|
|
|
for _, line := range AsciiBox([]string{"Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):", url}, 2) {
|
|
sc.log.Info().Msg(line)
|
|
}
|
|
|
|
if c.IsSet("notify") && c.IsSet("uname") && c.IsSet("key") {
|
|
sendMail(url, sc, c)
|
|
} else {
|
|
if !c.IsSet("uname") {
|
|
sc.log.Error().Msg("smtp error : Failed to send email. err(): --uname SMTP login username not specified")
|
|
}
|
|
|
|
if !c.IsSet("key") {
|
|
sc.log.Error().Msg("smtp error : Failed to send email. err(): --key SMTP login password not specified")
|
|
}
|
|
|
|
if !c.IsSet("notify") {
|
|
sc.log.Error().Msg("smtp error : Failed to send email. err(): --notify No receipient mail address specified")
|
|
}
|
|
}
|
|
|
|
if !sc.c.IsSet("protocol") {
|
|
sc.c.Set("protocol", "quic")
|
|
}
|
|
|
|
// Override the number of connections used. Quick tunnels shouldn't be used for production usage,
|
|
// so, use a single connection instead.
|
|
sc.c.Set(haConnectionsFlag, "1")
|
|
return StartServer(
|
|
sc.c,
|
|
buildInfo,
|
|
&connection.NamedTunnelProperties{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname},
|
|
sc.log,
|
|
)
|
|
}
|
|
|
|
type QuickTunnelResponse struct {
|
|
Success bool
|
|
Result QuickTunnel
|
|
Errors []QuickTunnelError
|
|
}
|
|
|
|
type QuickTunnelError struct {
|
|
Code int
|
|
Message string
|
|
}
|
|
|
|
type QuickTunnel struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Hostname string `json:"hostname"`
|
|
AccountTag string `json:"account_tag"`
|
|
Secret []byte `json:"secret"`
|
|
}
|
|
|
|
// Print out the given lines in a nice ASCII box.
|
|
func AsciiBox(lines []string, padding int) (box []string) {
|
|
maxLen := maxLen(lines)
|
|
spacer := strings.Repeat(" ", padding)
|
|
border := "+" + strings.Repeat("-", maxLen+(padding*2)) + "+"
|
|
box = append(box, border)
|
|
for _, line := range lines {
|
|
box = append(box, "|"+spacer+line+strings.Repeat(" ", maxLen-len(line))+spacer+"|")
|
|
}
|
|
box = append(box, border)
|
|
return
|
|
}
|
|
|
|
func maxLen(lines []string) int {
|
|
max := 0
|
|
for _, line := range lines {
|
|
if len(line) > max {
|
|
max = len(line)
|
|
}
|
|
}
|
|
return max
|
|
}
|