Last active
October 22, 2024 09:44
-
-
Save milanaleksic/162c99697a08bb36fe514f66399913ec to your computer and use it in GitHub Desktop.
Ngrok free mode always has only one single tunnel allowed. If you use TCP tunnel, this script extracts the value (since ngrok doesn't have server API). This way you can connect to that port from a script (ssh tunnel is my favorite usage of ngrok) instead of having to visit the site manually to get this value.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"errors" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"net/http/cookiejar" | |
"net/url" | |
"os" | |
"regexp" | |
"strings" | |
"flag" | |
) | |
const loginPage = "https://dashboard.ngrok.com/user/login" | |
var ( | |
debugMode bool | |
email string | |
password string | |
csrfPattern = regexp.MustCompile(`value="([^"]+)"`) | |
tunnelPattern = regexp.MustCompile(`tcp://\d+.tcp.ngrok.io:(\d+)`) | |
) | |
func init() { | |
flag.BoolVar(&debugMode, "debugMode", false, "show extra debug information") | |
flag.StringVar(&email, "email", "", "email used for ngrok") | |
flag.StringVar(&password, "password", "", "password used for ngrok") | |
flag.Parse() | |
} | |
func main() { | |
checkLogging() | |
jarOptions := &cookiejar.Options{} | |
jar, err := cookiejar.New(jarOptions) | |
check("cookie jar", err) | |
client := &http.Client{ | |
Jar: jar, | |
} | |
resp, err := client.Get(loginPage) | |
check("login page fetch", err) | |
csrf := getCSRF(resp) | |
resp.Body.Close() | |
form := url.Values{} | |
form.Add("email", email) | |
form.Add("password", password) | |
form.Add("csrf_token", csrf) | |
loginRequest, err := http.NewRequest("POST", loginPage, strings.NewReader(form.Encode())) | |
check("login request creation", err) | |
loginRequest.Header.Add("Referer", loginPage) | |
loginRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded") | |
resp, err = client.Do(loginRequest) | |
check("login page post", err) | |
// dumpResponse(resp) | |
resp.Body.Close() | |
resp, err = client.Get("https://dashboard.ngrok.com/status") | |
listTunnels(resp) | |
resp.Body.Close() | |
} | |
func check(reason string, err error) { | |
if err != nil { | |
log.Panicf("Can't process part '%v': error %v", reason, err) | |
} | |
} | |
func listTunnels(resp *http.Response) { | |
bufReader := bufio.NewReader(resp.Body) | |
for { | |
line, err := bufReader.ReadString(10) | |
if err == io.EOF { | |
break | |
} | |
check("bytes read", err) | |
if strings.Contains(line, "tcp") { | |
match := tunnelPattern.FindAllStringSubmatch(line, 1)[0][1] | |
log.Printf("Found port: %v", match) | |
fmt.Fprint(os.Stdout, match) | |
} | |
} | |
} | |
func getCSRF(resp *http.Response) string { | |
bufReader := bufio.NewReader(resp.Body) | |
for { | |
line, err := bufReader.ReadString(10) | |
if err == io.EOF { | |
break | |
} | |
check("bytes read", err) | |
if strings.Contains(line, "csrf") { | |
match := csrfPattern.FindAllStringSubmatch(line, 1)[0][1] | |
log.Printf("Found CSRF: %T, %v", match, match) | |
return match | |
} | |
} | |
panic("CSRF not found") | |
} | |
func dumpResponse(resp *http.Response) { | |
bufReader := bufio.NewReader(resp.Body) | |
for { | |
line, err := bufReader.ReadString(10) | |
if err == io.EOF { | |
break | |
} | |
check("response read", err) | |
log.Printf("RESPONSE: %v", line) | |
} | |
} | |
type blackHoleWriter struct { | |
} | |
func (w *blackHoleWriter) Write(p []byte) (n int, err error) { | |
err = errors.New("black hole writer") | |
return | |
} | |
func checkLogging() { | |
if !debugMode { | |
log.SetOutput(&blackHoleWriter{}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment