github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/cmd/derod/update.go (about)

     1  package main
     2  
     3  //import "fmt"
     4  import "net"
     5  import "time"
     6  import "io"
     7  
     8  //import "io/ioutil"
     9  //import "net/http"
    10  import "context"
    11  import "strings"
    12  import "math/rand"
    13  import "encoding/base64"
    14  import "encoding/json"
    15  import "runtime/debug"
    16  import "encoding/binary"
    17  
    18  //import "crypto/tls"
    19  
    20  import "github.com/blang/semver"
    21  import "github.com/miekg/dns"
    22  import "github.com/romana/rlog"
    23  
    24  import "github.com/deroproject/derosuite/config"
    25  import "github.com/deroproject/derosuite/globals"
    26  
    27  /* this needs to be set on update.dero.io. as TXT record,  in encoded form as base64
    28   *
    29  { "version" : "1.0.2",
    30    "message" : "\n\n\u001b[32m This is a mandatory update\u001b[0m",
    31    "critical" : ""
    32  }
    33  
    34  base64 eyAidmVyc2lvbiIgOiAiMS4wLjIiLAogIm1lc3NhZ2UiIDogIlxuXG5cdTAwMWJbMzJtIFRoaXMgaXMgYSBtYW5kYXRvcnkgdXBkYXRlXHUwMDFiWzBtIiwgCiJjcml0aWNhbCIgOiAiIiAKfQ==
    35  
    36  
    37  
    38  TXT record should be set as update=eyAidmVyc2lvbiIgOiAiMS4wLjIiLAogIm1lc3NhZ2UiIDogIlxuXG5cdTAwMWJbMzJtIFRoaXMgaXMgYSBtYW5kYXRvcnkgdXBkYXRlXHUwMDFiWzBtIiwgCiJjcml0aWNhbCIgOiAiIiAKfQ==
    39  */
    40  
    41  func check_update_loop() {
    42  
    43  	for {
    44  
    45  		if config.DNS_NOTIFICATION_ENABLED {
    46  
    47  			globals.Logger.Debugf("Checking update..")
    48  			check_update()
    49  		}
    50  		time.Sleep(2 * 3600 * time.Second) // check every 2 hours
    51  	}
    52  
    53  }
    54  
    55  // wrapper to make requests using proxy
    56  func dialContextwrapper(ctx context.Context, network, address string) (net.Conn, error) {
    57  	return globals.Dialer.Dial(network, address)
    58  }
    59  
    60  type socks_dialer net.Dialer
    61  
    62  func (d *socks_dialer) Dial(network, address string) (net.Conn, error) {
    63  	globals.Logger.Infof("Using our dial")
    64  	return globals.Dialer.Dial(network, address)
    65  }
    66  
    67  func (d *socks_dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
    68  	globals.Logger.Infof("Using our context dial")
    69  	return globals.Dialer.Dial(network, address)
    70  }
    71  
    72  func dial_random_read_response(in []byte) (out []byte, err error) {
    73  	defer func() {
    74  		if r := recover(); r != nil {
    75  			rlog.Warnf("Recovered while checking updates, Stack trace below", r)
    76  			rlog.Warnf("Stack trace  \n%s", debug.Stack())
    77  		}
    78  	}()
    79  
    80  	// since we may be connecting through socks, grab the remote ip for our purpose rightnow
    81  	//conn, err := globals.Dialer.Dial("tcp", "208.67.222.222:53")
    82  	//conn, err := net.Dial("tcp", "8.8.8.8:53")
    83  	random_feeder := rand.New(globals.NewCryptoRandSource())                          // use crypto secure resource
    84  	server_address := config.DNS_servers[random_feeder.Intn(len(config.DNS_servers))] // choose a random server cryptographically
    85  	conn, err := net.Dial("tcp", server_address)
    86  
    87  	//conn, err := tls.Dial("tcp", remote_ip.String(),&tls.Config{InsecureSkipVerify: true})
    88  	if err != nil {
    89  		rlog.Warnf("Dial failed err %s", err.Error())
    90  		return
    91  	}
    92  
    93  	defer conn.Close() // close connection at end
    94  
    95  	// upgrade connection TO TLS ( tls.Dial does NOT support proxy)
    96  	//conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
    97  
    98  	rlog.Tracef(1, "Sending %d bytes", len(in))
    99  
   100  	var buf [2]byte
   101  	binary.BigEndian.PutUint16(buf[:], uint16(len(in)))
   102  	conn.Write(buf[:]) // write length in bigendian format
   103  
   104  	conn.Write(in) // write data
   105  
   106  	// now we must wait for response to arrive
   107  	var frame_length_buf [2]byte
   108  
   109  	conn.SetReadDeadline(time.Now().Add(20 * time.Second))
   110  	nbyte, err := io.ReadFull(conn, frame_length_buf[:])
   111  	if err != nil || nbyte != 2 {
   112  		// error while reading from connection we must disconnect it
   113  		rlog.Warnf("Could not read DNS length prefix err %s", err)
   114  		return
   115  	}
   116  
   117  	frame_length := binary.BigEndian.Uint16(frame_length_buf[:])
   118  	if frame_length == 0 {
   119  		// most probably memory DDOS attack, kill the connection
   120  		rlog.Warnf("Frame length is too small")
   121  		return
   122  	}
   123  
   124  	out = make([]byte, frame_length)
   125  
   126  	conn.SetReadDeadline(time.Now().Add(20 * time.Second))
   127  	data_size, err := io.ReadFull(conn, out)
   128  	if err != nil || data_size <= 0 || uint16(data_size) != frame_length {
   129  		// error while reading from connection we must kiil it
   130  		rlog.Warnf("Could not read  DNS data size  read %d, frame length %d err %s", data_size, frame_length, err)
   131  		return
   132  
   133  	}
   134  	out = out[:frame_length]
   135  
   136  	return
   137  }
   138  
   139  func check_update() {
   140  
   141  	// add panic handler, in case DNS acts rogue and tries to attack
   142  	defer func() {
   143  		if r := recover(); r != nil {
   144  			rlog.Warnf("Recovered while checking updates, Stack trace below", r)
   145  			rlog.Warnf("Stack trace  \n%s", debug.Stack())
   146  		}
   147  	}()
   148  
   149  	if !config.DNS_NOTIFICATION_ENABLED { // if DNS notifications are disabled bail out
   150  		return
   151  	}
   152  
   153  	/*  var u update_message
   154  	    u.Version = "2.0.0"
   155  	    u.Message = "critical msg txt\x1b[35m should \n be in RED"
   156  
   157  	     globals.Logger.Infof("testing %s",u.Message)
   158  
   159  	     j,err := json.Marshal(u)
   160  	     globals.Logger.Infof("json format %s err %s",j,err)
   161  	*/
   162  	/*extract_parse_version("update=eyAidmVyc2lvbiIgOiAiMS4xLjAiLCAibWVzc2FnZSIgOiAiXG5cblx1MDAxYlszMm0gVGhpcyBpcyBhIG1hbmRhdG9yeSB1cGdyYWRlIHBsZWFzZSB1cGdyYWRlIGZyb20geHl6IFx1MDAxYlswbSIsICJjcml0aWNhbCIgOiAiIiB9")
   163  
   164  	  return
   165  	*/
   166  
   167  	m1 := new(dns.Msg)
   168  	// m1.SetEdns0(65000, true), dnssec probably leaks current timestamp, it's disabled until more invetigation
   169  	m1.Id = dns.Id()
   170  	m1.RecursionDesired = true
   171  	m1.Question = make([]dns.Question, 1)
   172  	m1.Question[0] = dns.Question{config.DNS_UPDATE_CHECK, dns.TypeTXT, dns.ClassINET}
   173  
   174  	packed, err := m1.Pack()
   175  	if err != nil {
   176  		globals.Logger.Warnf("Error which packing DNS query for program update err %s", err)
   177  		return
   178  	}
   179  
   180  	/*
   181  
   182  			// setup a http client
   183  			httpTransport := &http.Transport{}
   184  			httpClient := &http.Client{Transport: httpTransport}
   185  			// set our socks5 as the dialer
   186  			httpTransport.Dial = globals.Dialer.Dial
   187  
   188  
   189  
   190  		        packed_base64:= base64.RawURLEncoding.EncodeToString(packed)
   191  		response, err := httpClient.Get("https://1.1.1.1/dns-query?ct=application/dns-udpwireformat&dns="+packed_base64)
   192  
   193  		_ = packed_base64
   194  
   195  		if err != nil {
   196  		    rlog.Warnf("error making DOH request err %s",err)
   197  		    return
   198  		}
   199  
   200  		defer response.Body.Close()
   201  		        contents, err := ioutil.ReadAll(response.Body)
   202  		        if err != nil {
   203  		            rlog.Warnf("error reading DOH response err %s",err)
   204  		            return
   205  		}
   206  	*/
   207  
   208  	contents, err := dial_random_read_response(packed)
   209  	if err != nil {
   210  		rlog.Warnf("error reading response from DNS server err %s", err)
   211  		return
   212  
   213  	}
   214  
   215  	rlog.Debugf("DNS response length from DNS server %d bytes", len(contents))
   216  
   217  	err = m1.Unpack(contents)
   218  	if err != nil {
   219  		rlog.Warnf("error decoding DOH response err %s", err)
   220  		return
   221  
   222  	}
   223  
   224  	for i := range m1.Answer {
   225  		if t, ok := m1.Answer[i].(*dns.TXT); ok {
   226  
   227  			// replace any spaces so as records could be joined
   228  
   229  			rlog.Tracef(1, "Process record %+v", t.Txt)
   230  			joined := strings.Join(t.Txt, "")
   231  			extract_parse_version(joined)
   232  
   233  		}
   234  	}
   235  
   236  	//globals.Logger.Infof("response %+v err ",m1,err)
   237  
   238  }
   239  
   240  type update_message struct {
   241  	Version  string `json:"version"`
   242  	Message  string `json:"message"`
   243  	Critical string `json:"critical"` // always broadcasted, without checks for version
   244  }
   245  
   246  // our version are TXT record of following format
   247  // version=base64 encoded json
   248  func extract_parse_version(str string) {
   249  
   250  	strl := strings.ToLower(str)
   251  	if !strings.HasPrefix(strl, "update=") {
   252  		rlog.Tracef(1, "Skipping record %s", str)
   253  		return
   254  	}
   255  
   256  	parts := strings.SplitN(str, "=", 2)
   257  	if len(parts) != 2 {
   258  		return
   259  	}
   260  	rlog.Tracef(1, "parts %s", parts[1])
   261  
   262  	data, err := base64.StdEncoding.DecodeString(parts[1])
   263  	if err != nil {
   264  		rlog.Tracef(1, "Could NOT decode base64 update message %s", err)
   265  		return
   266  	}
   267  
   268  	var u update_message
   269  	err = json.Unmarshal(data, &u)
   270  
   271  	//globals.Logger.Infof("data %+v", u)
   272  
   273  	if err != nil {
   274  		rlog.Tracef(1, "Could NOT decode json update message %s", err)
   275  		return
   276  	}
   277  
   278  	uversion, err := semver.ParseTolerant(u.Version)
   279  	if err != nil {
   280  		rlog.Tracef(1, "Could NOT update version %s", err)
   281  	}
   282  
   283  	current_version := config.Version
   284  	current_version.Pre = current_version.Pre[:0]
   285  	current_version.Build = current_version.Build[:0]
   286  
   287  	// give warning to update the daemon
   288  	if u.Message != "" && err == nil { // check semver
   289  		if current_version.LT(uversion) {
   290  			if current_version.Major != uversion.Major { // if major version is different give extract warning
   291  				globals.Logger.Infof("\033[31m CRITICAL MAJOR update, please upgrade ASAP.\033[0m")
   292  			}
   293  
   294  			globals.Logger.Infof("%s", u.Message) // give the version upgrade message
   295  			globals.Logger.Infof("\033[33mCurrent Version %s \033[32m-> Upgrade Version %s\033[0m ", current_version.String(), uversion.String())
   296  		}
   297  	}
   298  
   299  	if u.Critical != "" { // give the critical upgrade message
   300  		globals.Logger.Infof("%s", u.Critical)
   301  	}
   302  
   303  }