315 lines
9.2 KiB
Go
Raw Permalink Normal View History

2021-12-04 16:42:11 +00:00
// Copyright 2017-2021 DERO Project. All rights reserved.
// Use of this source code in any form is governed by RESEARCH license.
// license can be found in the LICENSE file.
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import "fmt"
import "net"
import "time"
import "io"
//import "io/ioutil"
//import "net/http"
import "context"
import "strings"
import "math/rand"
import "encoding/base64"
import "encoding/json"
import "runtime/debug"
import "encoding/binary"
//import "crypto/tls"
import "github.com/blang/semver/v4"
import "github.com/miekg/dns"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/globals"
/* this needs to be set on update.dero.io. as TXT record, in encoded form as base64
*
{ "version" : "1.0.2",
"message" : "\n\n\u001b[32m This is a mandatory update\u001b[0m",
"critical" : ""
}
base64 eyAidmVyc2lvbiIgOiAiMS4wLjIiLAogIm1lc3NhZ2UiIDogIlxuXG5cdTAwMWJbMzJtIFRoaXMgaXMgYSBtYW5kYXRvcnkgdXBkYXRlXHUwMDFiWzBtIiwgCiJjcml0aWNhbCIgOiAiIiAKfQ==
TXT record should be set as update=eyAidmVyc2lvbiIgOiAiMS4wLjIiLAogIm1lc3NhZ2UiIDogIlxuXG5cdTAwMWJbMzJtIFRoaXMgaXMgYSBtYW5kYXRvcnkgdXBkYXRlXHUwMDFiWzBtIiwgCiJjcml0aWNhbCIgOiAiIiAKfQ==
*/
func check_update_loop() {
for {
if config.DNS_NOTIFICATION_ENABLED {
globals.Logger.V(2).Info("Checking update..")
check_update()
}
time.Sleep(2 * 3600 * time.Second) // check every 2 hours
}
}
// wrapper to make requests using proxy
func dialContextwrapper(ctx context.Context, network, address string) (net.Conn, error) {
return globals.Dialer.Dial(network, address)
}
type socks_dialer net.Dialer
func (d *socks_dialer) Dial(network, address string) (net.Conn, error) {
return globals.Dialer.Dial(network, address)
}
func (d *socks_dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return globals.Dialer.Dial(network, address)
}
func dial_random_read_response(in []byte) (out []byte, err error) {
defer func() {
if r := recover(); r != nil {
logger.V(2).Error(nil, "Recovered while checking updates", "r", r, "stack", debug.Stack())
}
}()
// since we may be connecting through socks, grab the remote ip for our purpose rightnow
//conn, err := globals.Dialer.Dial("tcp", "208.67.222.222:53")
//conn, err := net.Dial("tcp", "8.8.8.8:53")
random_feeder := rand.New(globals.NewCryptoRandSource()) // use crypto secure resource
server_address := config.DNS_servers[random_feeder.Intn(len(config.DNS_servers))] // choose a random server cryptographically
conn, err := net.Dial("tcp", server_address)
//conn, err := tls.Dial("tcp", remote_ip.String(),&tls.Config{InsecureSkipVerify: true})
if err != nil {
logger.V(2).Error(err, "Dial failed ")
return
}
defer conn.Close() // close connection at end
// upgrade connection TO TLS ( tls.Dial does NOT support proxy)
//conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
//rlog.Tracef(1, "Sending %d bytes", len(in))
var buf [2]byte
binary.BigEndian.PutUint16(buf[:], uint16(len(in)))
conn.Write(buf[:]) // write length in bigendian format
conn.Write(in) // write data
// now we must wait for response to arrive
var frame_length_buf [2]byte
conn.SetReadDeadline(time.Now().Add(20 * time.Second))
nbyte, err := io.ReadFull(conn, frame_length_buf[:])
if err != nil || nbyte != 2 {
// error while reading from connection we must disconnect it
logger.V(2).Error(err, "Could not read DNS length prefix")
return
}
frame_length := binary.BigEndian.Uint16(frame_length_buf[:])
if frame_length == 0 {
// most probably memory DDOS attack, kill the connection
logger.V(2).Error(nil, "Frame length is too small")
return
}
out = make([]byte, frame_length)
conn.SetReadDeadline(time.Now().Add(20 * time.Second))
data_size, err := io.ReadFull(conn, out)
if err != nil || data_size <= 0 || uint16(data_size) != frame_length {
// error while reading from connection we must kiil it
//rlog.Warnf("Could not read DNS data size read %d, frame length %d err %s", data_size, frame_length, err)
logger.V(2).Error(err, "Could not read DNS data")
return
}
out = out[:frame_length]
return
}
func check_update() {
// add panic handler, in case DNS acts rogue and tries to attack
defer func() {
if r := recover(); r != nil {
logger.V(2).Error(nil, "Recovered while checking updates", r, "r", "stack", debug.Stack())
}
}()
if !config.DNS_NOTIFICATION_ENABLED { // if DNS notifications are disabled bail out
return
}
/* var u update_message
u.Version = "2.0.0"
u.Message = "critical msg txt\x1b[35m should \n be in RED"
globals.Logger.Infof("testing %s",u.Message)
j,err := json.Marshal(u)
globals.Logger.Infof("json format %s err %s",j,err)
*/
/*extract_parse_version("update=eyAidmVyc2lvbiIgOiAiMS4xLjAiLCAibWVzc2FnZSIgOiAiXG5cblx1MDAxYlszMm0gVGhpcyBpcyBhIG1hbmRhdG9yeSB1cGdyYWRlIHBsZWFzZSB1cGdyYWRlIGZyb20geHl6IFx1MDAxYlswbSIsICJjcml0aWNhbCIgOiAiIiB9")
return
*/
m1 := new(dns.Msg)
// m1.SetEdns0(65000, true), dnssec probably leaks current timestamp, it's disabled until more invetigation
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{Name: config.DNS_UPDATE_CHECK, Qtype: dns.TypeTXT, Qclass: dns.ClassINET}
packed, err := m1.Pack()
if err != nil {
globals.Logger.V(2).Error(err, "Error which packing DNS query for program update")
return
}
/*
// setup a http client
httpTransport := &http.Transport{}
httpClient := &http.Client{Transport: httpTransport}
// set our socks5 as the dialer
httpTransport.Dial = globals.Dialer.Dial
packed_base64:= base64.RawURLEncoding.EncodeToString(packed)
response, err := httpClient.Get("https://1.1.1.1/dns-query?ct=application/dns-udpwireformat&dns="+packed_base64)
_ = packed_base64
if err != nil {
rlog.Warnf("error making DOH request err %s",err)
return
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
rlog.Warnf("error reading DOH response err %s",err)
return
}
*/
contents, err := dial_random_read_response(packed)
if err != nil {
logger.V(2).Error(err, "error reading response from DNS server")
return
}
//rlog.Debugf("DNS response length from DNS server %d bytes", len(contents))
err = m1.Unpack(contents)
if err != nil {
logger.V(2).Error(err, "error decoding DOH response")
return
}
for i := range m1.Answer {
if t, ok := m1.Answer[i].(*dns.TXT); ok {
// replace any spaces so as records could be joined
logger.V(2).Info("Processing record ", "record", t.Txt)
joined := strings.Join(t.Txt, "")
extract_parse_version(joined)
}
}
//globals.Logger.Infof("response %+v err ",m1,err)
}
type update_message struct {
Version string `json:"version"`
Message string `json:"message"`
Critical string `json:"critical"` // always broadcasted, without checks for version
}
// our version are TXT record of following format
// version=base64 encoded json
func extract_parse_version(str string) {
strl := strings.ToLower(str)
if !strings.HasPrefix(strl, "update=") {
logger.V(2).Info("Skipping record", "record", str)
return
}
parts := strings.SplitN(str, "=", 2)
if len(parts) != 2 {
return
}
data, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
logger.V(2).Error(err, "Could NOT decode base64 update message", "data", parts[1])
return
}
var u update_message
err = json.Unmarshal(data, &u)
//globals.Logger.Infof("data %+v", u)
if err != nil {
logger.V(2).Error(err, "Could NOT decode json update message")
return
}
uversion, err := semver.ParseTolerant(u.Version)
if err != nil {
logger.V(2).Error(err, "Could NOT update version")
}
current_version := config.Version
current_version.Pre = current_version.Pre[:0]
current_version.Build = current_version.Build[:0]
// give warning to update the daemon
if u.Message != "" && err == nil { // check semver
if current_version.LT(uversion) {
if current_version.Major != uversion.Major { // if major version is different give extract warning
logger.Info("\033[31m CRITICAL MAJOR update, please upgrade ASAP.\033[0m")
}
logger.Info(fmt.Sprintf("%s", u.Message)) // give the version upgrade message
logger.Info(fmt.Sprintf("\033[33mCurrent Version %s \033[32m-> Upgrade Version %s\033[0m ", current_version.String(), uversion.String()))
}
}
if u.Critical != "" { // give the critical upgrade message
logger.Info(fmt.Sprintf("%s", u.Critical))
}
}