github.com/la5nta/wl2k-go@v0.11.8/transport/telnet/dial.go (about) 1 // Copyright 2015 Martin Hebnes Pedersen (LA5NTA). All rights reserved. 2 // Use of this source code is governed by the MIT-license that can be 3 // found in the LICENSE file. 4 5 // Package telnet provides a method of connecting to Winlink CMS over tcp ("telnet-mode") 6 package telnet 7 8 import ( 9 "bufio" 10 "context" 11 "fmt" 12 "net" 13 "strings" 14 "time" 15 16 "github.com/la5nta/wl2k-go/transport" 17 ) 18 19 const ( 20 CMSTargetCall = "wl2k" 21 CMSPassword = "CMSTelnet" 22 CMSAddress = "server.winlink.org:8772" 23 ) 24 25 var DefaultDialer = &Dialer{Timeout: 30 * time.Second} 26 27 func init() { 28 transport.RegisterDialer("telnet", DefaultDialer) 29 } 30 31 // DialCMS dials a random CMS server through server.winlink.org. 32 // 33 // The function will retry 4 times before giving up and returning an error. 34 func DialCMS(mycall string) (net.Conn, error) { 35 var conn net.Conn 36 var err error 37 38 // Dial with retry, in case we hit an unavailable CMS. 39 for i := 0; i < 4; i++ { 40 conn, err = Dial(CMSAddress, mycall, CMSPassword) 41 if err == nil { 42 break 43 } 44 } 45 46 return conn, err 47 } 48 49 // Dialer implements the transport.Dialer interface. 50 type Dialer struct{ Timeout time.Duration } 51 52 func (d Dialer) DialURLContext(ctx context.Context, url *transport.URL) (net.Conn, error) { 53 if url.Scheme != "telnet" { 54 return nil, transport.ErrUnsupportedScheme 55 } 56 57 var user, pass string 58 if url.User != nil { 59 pass, _ = url.User.Password() 60 user = url.User.Username() 61 } 62 63 timeout := d.Timeout 64 if str := url.Params.Get("dial_timeout"); str != "" { 65 dur, err := time.ParseDuration(str) 66 if err != nil { 67 return nil, fmt.Errorf("invalid dial_timeout value: %w", err) 68 } 69 timeout = dur 70 } 71 if timeout > 0 { 72 c, cancel := context.WithTimeout(ctx, timeout) 73 defer cancel() 74 ctx = c 75 } 76 return DialContext(ctx, url.Host, user, pass) 77 } 78 79 // DialURL dials telnet:// URLs 80 // 81 // The URL parameter dial_timeout can be used to set a custom dial timeout interval. E.g. "2m". 82 func (d Dialer) DialURL(url *transport.URL) (net.Conn, error) { 83 return d.DialURLContext(context.Background(), url) 84 } 85 86 func Dial(addr, mycall, password string) (net.Conn, error) { 87 return DialTimeout(addr, mycall, password, 5*time.Second) 88 } 89 90 func DialTimeout(addr, mycall, password string, timeout time.Duration) (net.Conn, error) { 91 ctx, cancel := context.WithTimeout(context.Background(), timeout) 92 defer cancel() 93 return DialContext(ctx, addr, mycall, password) 94 } 95 96 func DialContext(ctx context.Context, addr, mycall, password string) (net.Conn, error) { 97 var d net.Dialer 98 conn, err := d.DialContext(ctx, `tcp`, addr) 99 if err != nil { 100 return nil, err 101 } 102 103 // Log in to telnet server 104 reader := bufio.NewReader(conn) 105 L: 106 for { 107 line, err := reader.ReadString('\r') 108 line = strings.TrimSpace(strings.ToLower(line)) 109 switch { 110 case err != nil: 111 conn.Close() 112 return nil, fmt.Errorf("Error while logging in: %s", err) 113 case strings.HasPrefix(line, "callsign"): 114 fmt.Fprintf(conn, "%s\r", mycall) 115 case strings.HasPrefix(line, "password"): 116 fmt.Fprintf(conn, "%s\r", password) 117 break L 118 } 119 } 120 121 return &Conn{conn, CMSTargetCall}, nil 122 }