OTP support

This commit is contained in:
Xavier Henner 2019-08-18 16:29:37 +02:00
parent 07b37e9bc4
commit e9f6a04864
7 changed files with 118 additions and 32 deletions

View File

@ -7,16 +7,16 @@ type DailymotionVPN struct {
func (s *DailymotionVPN) ServerList() (error, *map[string]string) { func (s *DailymotionVPN) ServerList() (error, *map[string]string) {
VPNNames := map[string]string{ VPNNames := map[string]string{
"Paris 1": "gate-01.dc3.dailmotion.com", "Paris 1": "gate-01.dc3.dailymotion.com",
"New-York 1": "gate-01.nyc.dailmotion.com", "New-York 1": "gate-01.nyc.dailymotion.com",
"Silicon Valley 1": "gate-01.sv6.dailmotion.com", "Silicon Valley 1": "gate-01.sv6.dailymotion.com",
"Singapore 1": "gate-01.sg1.dailmotion.com", "Singapore 1": "gate-01.sg1.dailymotion.com",
"Tokyo 1": "gate-01.ty4.dailmotion.com", "Tokyo 1": "gate-01.ty4.dailymotion.com",
"Paris 2": "gate-01.dc3.dailmotion.com", "Paris 2": "gate-02.dc3.dailymotion.com",
"New-York 2": "gate-01.nyc.dailmotion.com", "New-York 2": "gate-02.nyc.dailymotion.com",
"Silicon Valley 2": "gate-01.sv6.dailmotion.com", "Silicon Valley 2": "gate-02.sv6.dailymotion.com",
"Singapore 2": "gate-01.sg1.dailmotion.com", "Singapore 2": "gate-02.sg1.dailymotion.com",
"Tokyo 2": "gate-01.ty4.dailmotion.com", "Tokyo 2": "gate-02.ty4.dailymotion.com",
"default": "vpn.dailymotion.com", "default": "vpn.dailymotion.com",
} }
return nil, &VPNNames return nil, &VPNNames

View File

@ -22,6 +22,7 @@ type jsonInputParams struct {
Session int `json:"session"` Session int `json:"session"`
User string `json:"user"` User string `json:"user"`
Pass string `json:"password"` Pass string `json:"password"`
OTP string `json:"otp"`
} }
type HttpServer struct { type HttpServer struct {
@ -84,7 +85,7 @@ func (h *HttpServer) ajaxHandler(w http.ResponseWriter, r *http.Request) {
err = h.ovpn.SetRemote(req.Params.Server, req.Params.Session) err = h.ovpn.SetRemote(req.Params.Server, req.Params.Session)
jsonStr = []byte("{\"status\": \"ok\"}") jsonStr = []byte("{\"status\": \"ok\"}")
case "auth-user-pass": case "auth-user-pass":
err = h.ovpn.AuthUserPass(req.Params.Session, req.Params.User, req.Params.Pass) err = h.ovpn.AuthUserPass(req.Params.Session, req.Params.User, req.Params.Pass, req.Params.OTP)
jsonStr = []byte("{\"status\": \"ok\"}") jsonStr = []byte("{\"status\": \"ok\"}")
case "get-sessions": case "get-sessions":
jsonStr, err = json.Marshal(h.ovpn) jsonStr, err = json.Marshal(h.ovpn)

View File

@ -2,6 +2,7 @@ package main
import ( import (
"bufio" "bufio"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -14,6 +15,7 @@ import (
type OpenVpnPassword struct { type OpenVpnPassword struct {
User string User string
Pass string Pass string
OTP string
} }
type OpenVpnSrv struct { type OpenVpnSrv struct {
@ -21,6 +23,7 @@ type OpenVpnSrv struct {
Status string `json:"status"` Status string `json:"status"`
Provider string `json:"provider"` Provider string `json:"provider"`
Identifier string `json:"identifier"` Identifier string `json:"identifier"`
Message string `json:"message"`
chanHold chan bool chanHold chan bool
chanPass chan OpenVpnPassword chanPass chan OpenVpnPassword
m sync.RWMutex m sync.RWMutex
@ -28,6 +31,7 @@ type OpenVpnSrv struct {
buf *bufio.ReadWriter buf *bufio.ReadWriter
mgt *OpenVpnMgt mgt *OpenVpnMgt
hold bool hold bool
authWait bool
authCache *OpenVpnPassword authCache *OpenVpnPassword
} }
@ -47,12 +51,21 @@ func NewOpenVpnSrv(conn net.Conn, mgt *OpenVpnMgt) *OpenVpnSrv {
ret: make(chan []string), ret: make(chan []string),
mgt: mgt, mgt: mgt,
hold: false, hold: false,
authWait: false,
Status: "Starting", Status: "Starting",
Identifier: "Unknown", Identifier: "Unknown",
Provider: "Unknown", Provider: "Unknown",
} }
} }
func (v *OpenVpnSrv) B64OTP() string {
return base64.StdEncoding.EncodeToString([]byte(v.authCache.OTP))
}
func (v *OpenVpnSrv) B64Pass() string {
return base64.StdEncoding.EncodeToString([]byte(v.authCache.Pass))
}
// send a command to the server. Set the channel to receive the response // send a command to the server. Set the channel to receive the response
func (v *OpenVpnSrv) sendCommand(msg []string) (error, []string) { func (v *OpenVpnSrv) sendCommand(msg []string) (error, []string) {
v.Lock() v.Lock()
@ -149,7 +162,9 @@ func (v *OpenVpnSrv) GetLine() (string, error) {
} }
func (v *OpenVpnSrv) ValidRemote(server, port, proto string) { func (v *OpenVpnSrv) ValidRemote(server, port, proto string) {
if !v.authWait {
v.Status = "Connected" v.Status = "Connected"
}
if v.Remote != "" { if v.Remote != "" {
v.sendCommand([]string{fmt.Sprintf("remote MOD %s %s %s", v.Remote, port, proto)}) v.sendCommand([]string{fmt.Sprintf("remote MOD %s %s %s", v.Remote, port, proto)})
return return
@ -182,12 +197,31 @@ func (v *OpenVpnSrv) SetRemote(server string) error {
} }
func (v *OpenVpnSrv) NeedPassword(line string) { func (v *OpenVpnSrv) NeedPassword(line string) {
if v.authWait {
return
}
v.authWait = true
v.mgt.Debug(line) v.mgt.Debug(line)
v.Status = "Need Password" v.Status = "Need Password"
switch line { v.Message = ""
case ">PASSWORD:Need 'Auth' username/password": backup := v.authCache
OtpPassRegexp := rcache.Get("^>PASSWORD:Need 'Auth' username/password SC:([01]),(.*)$")
OtpPassMatch := OtpPassRegexp.FindStringSubmatch(line)
CRV1Regexep := rcache.Get(">PASSWORD:Verification Failed: 'Auth' \\['CRV1:([R,E]*):(.*):(.*):(.*)'\\]")
CRV1Match := CRV1Regexep.FindStringSubmatch(line)
switch {
case len(CRV1Match) > 1:
v.Status = "Need OTP"
v.Message = CRV1Match[4]
v.authCache = nil
case len(OtpPassMatch) > 1:
v.Status = "Need Password and OTP"
v.Message = OtpPassMatch[2]
case line == ">PASSWORD:Need 'Auth' username/password":
v.Status = "Need Password" v.Status = "Need Password"
case ">PASSWORD:Verification Failed: 'Auth'": case line == ">PASSWORD:Verification Failed: 'Auth'":
v.authCache = nil v.authCache = nil
v.Status = "Auth Failed" v.Status = "Auth Failed"
return return
@ -196,20 +230,55 @@ func (v *OpenVpnSrv) NeedPassword(line string) {
ident := <-v.chanPass ident := <-v.chanPass
v.authCache = &ident v.authCache = &ident
} }
switch line { var cmd []string
case ">PASSWORD:Need 'Auth' username/password": switch {
v.sendCommand([]string{fmt.Sprintf("username \"Auth\" %s", v.authCache.User)}) case len(CRV1Match) > 1:
v.sendCommand([]string{fmt.Sprintf("password \"Auth\" %s", v.authCache.Pass)}) cmd = []string{
fmt.Sprintf("username \"Auth\" %s", B64decode(CRV1Match[3])),
fmt.Sprintf("password \"Auth\" CRV1::%s::%s", CRV1Match[2], v.authCache.OTP),
} }
// restore auth backup
v.authCache = backup
case len(OtpPassMatch) > 1:
cmd = []string{
fmt.Sprintf("username \"Auth\" %s", v.authCache.User),
fmt.Sprintf("password \"Auth\" SCRV1:%s:%s", v.B64Pass(), v.B64OTP()),
}
case line == ">PASSWORD:Need 'Auth' username/password":
cmd = []string{
fmt.Sprintf("username \"Auth\" %s", v.authCache.User),
fmt.Sprintf("password \"Auth\" %s", v.authCache.Pass),
}
}
for _, c := range cmd {
v.sendCommand([]string{c})
}
v.authWait = false
v.Message = ""
v.Status = "Connected"
} }
func (v *OpenVpnSrv) AuthUserPass(user, pass string) { func (v *OpenVpnSrv) AuthUserPass(user, pass, otp string) {
auth := OpenVpnPassword{user, pass} auth := OpenVpnPassword{user, pass, otp}
if v.authCache == nil {
v.authCache = &auth v.authCache = &auth
if v.Status == "Need Password" {
v.Status = "Authenticate"
v.chanPass <- auth
} }
switch v.Status {
case "Need Password and OTP":
v.authCache.User = user
v.authCache.Pass = pass
v.authCache.OTP = otp
case "Need OTP":
v.authCache.OTP = otp
case "Need Password":
v.authCache.User = user
v.authCache.Pass = pass
default:
return
}
v.chanPass <- auth
v.Status = "Check Password"
} }
func (v *OpenVpnSrv) waitForRelase() { func (v *OpenVpnSrv) waitForRelase() {

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"encoding/base64"
"math/big" "math/big"
"net" "net"
"sort" "sort"
@ -8,6 +9,14 @@ import (
"github.com/pyke369/golang-support/uconfig" "github.com/pyke369/golang-support/uconfig"
) )
func B64decode(s string) string {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return ""
}
return string(data)
}
// check if there is a member of "search" in "list". sort list to be more // check if there is a member of "search" in "list". sort list to be more
// efficiant. adapt with sorting "search" too // efficiant. adapt with sorting "search" too
func inArray(search, list []string) bool { func inArray(search, list []string) bool {

View File

@ -102,13 +102,13 @@ func (s *OpenVpnMgt) Restart(pid int) error {
return nil return nil
} }
func (s *OpenVpnMgt) AuthUserPass(pid int, user, pass string) error { func (s *OpenVpnMgt) AuthUserPass(pid int, user, pass, otp string) error {
// check if the session is valid // check if the session is valid
err, session := s.GetSession(pid) err, session := s.GetSession(pid)
if err != nil { if err != nil {
return err return err
} }
session.AuthUserPass(user, pass) session.AuthUserPass(user, pass, otp)
return nil return nil
} }

View File

@ -142,7 +142,9 @@ select.interface {
function auth(pid) { function auth(pid) {
ajax({action: "auth-user-pass", params:{session: pid, ajax({action: "auth-user-pass", params:{session: pid,
user:$('#auth_'+pid+'>input.user').val(), user:$('#auth_'+pid+'>input.user').val(),
password:$('#auth_'+pid+'>input.pass').val()}}); password:$('#auth_'+pid+'>input.pass').val(),
otp:$('#auth_'+pid+'>input.otp').val()
}});
} }
function refresh(data) { function refresh(data) {
@ -201,11 +203,16 @@ select.interface {
line += '</td>\n'; line += '</td>\n';
line += '</tr>\n'; line += '</tr>\n';
if(infos['status']=="Need Password") { if(infos['status'].startsWith("Need")) {
line +='<tr><td colspan="4">'; line +='<tr><td colspan="4">';
line +='<form id="auth_'+pid+'" action="#" onsubmit="auth('+pid+') ; return false" >'; line +='<form id="auth_'+pid+'" action="#" onsubmit="auth('+pid+') ; return false" >';
if (infos['status'].includes("Password")) {
line +='<input class="user" placeholder="Enter login" size="15">'; line +='<input class="user" placeholder="Enter login" size="15">';
line +='<input type="password" placeholder="Password" size="15" class="pass">'; line +='<input type="password" placeholder="Password" size="15" class="pass">';
}
if (infos['status'].includes("OTP")) {
line +='<input class="otp" placeholder="'+infos["message"]+'" size="15">';
}
line +='<input type="submit" value="G0">'; line +='<input type="submit" value="G0">';
line +='</form>'; line +='</form>';
line +='</td></tr>\n'; line +='</td></tr>\n';