dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

actions.go (11335B)


      1 package actions
      2 
      3 import (
      4 	"bytes"
      5 	captcha "dkforest/pkg/captcha"
      6 	"dkforest/pkg/color"
      7 	"dkforest/pkg/config"
      8 	"dkforest/pkg/database"
      9 	dutils "dkforest/pkg/database/utils"
     10 	"dkforest/pkg/managers"
     11 	"dkforest/pkg/utils"
     12 	"dkforest/pkg/web"
     13 	"dkforest/pkg/web/handlers/interceptors"
     14 	"dkforest/pkg/web/handlers/poker"
     15 	"fmt"
     16 	"github.com/mattn/go-colorable"
     17 	wallet1 "github.com/monero-ecosystem/go-monero-rpc-client/wallet"
     18 	migrate "github.com/rubenv/sql-migrate"
     19 	"github.com/sirupsen/logrus"
     20 	"github.com/skratchdot/open-golang/open"
     21 	cli "github.com/urfave/cli/v2"
     22 	"log"
     23 	"math/rand"
     24 	"os"
     25 	"path/filepath"
     26 	"runtime"
     27 	"strconv"
     28 	"strings"
     29 	"time"
     30 )
     31 
     32 func Start(c *cli.Context) error {
     33 	rand.Seed(time.Now().UnixNano())
     34 	log.SetFlags(log.Ltime | log.Lshortfile)
     35 	logrus.SetFormatter(LogFormatter{})
     36 	logrus.SetOutput(colorable.NewColorableStderr())
     37 
     38 	logrus.Info("DarkForest v" + config.Global.AppVersion.Get().String())
     39 
     40 	port := c.Int("port")
     41 	host := c.String("host")
     42 	noBrowser := c.Bool("no-browser")
     43 
     44 	config.Global.CookieSecure.Set(c.Bool("cookie-secure"))
     45 	config.Global.CookieDomain.Set(c.String("cookie-domain"))
     46 
     47 	ensureProjectHome()
     48 
     49 	dbPath := filepath.Join(config.Global.ProjectPath.Get(), config.DbFileName)
     50 	db := database.NewDkfDB(dbPath)
     51 
     52 	runMigrations(db)
     53 
     54 	config.IsFirstUse.Store(isFirstUse(db))
     55 
     56 	captcha.SetCustomStore(captcha.NewMemoryStore(captcha.CollectNum, 120*time.Second))
     57 
     58 	if false {
     59 		err := db.DB().Debug().Exec(`
     60 	`).Error
     61 		logrus.Error(err)
     62 	}
     63 
     64 	db.GetPokerCasino()
     65 	settings := db.GetSettings()
     66 	config.ProtectHome.Store(settings.ProtectHome)
     67 	config.HomeUsersList.Store(settings.HomeUsersList)
     68 	config.ForceLoginCaptcha.Store(settings.ForceLoginCaptcha)
     69 	config.SignupEnabled.Store(settings.SignupEnabled)
     70 	config.SignupFakeEnabled.Store(settings.SignupFakeEnabled)
     71 	config.DownloadsEnabled.Store(settings.DownloadsEnabled)
     72 	config.ForumEnabled.Store(settings.ForumEnabled)
     73 	config.SilentSelfKick.Store(settings.SilentSelfKick)
     74 	config.MaybeAuthEnabled.Store(settings.MaybeAuthEnabled)
     75 	config.PowEnabled.Store(settings.PowEnabled)
     76 	config.PokerWithdrawEnabled.Store(settings.PokerWithdrawEnabled)
     77 	config.CaptchaDifficulty.Store(settings.CaptchaDifficulty)
     78 	config.MoneroPrice.Store(settings.MoneroPrice)
     79 
     80 	utils.SGo(func() { cleanupDatabase(db) })
     81 	utils.SGo(func() { managers.ActiveUsers.CleanupUsersCache() })
     82 	utils.SGo(func() { xmrCheck(db) })
     83 	utils.SGo(func() { xmrWatch(db) })
     84 	utils.SGo(func() { openBrowser(noBrowser, int64(port)) })
     85 
     86 	interceptors.LoadFilters(db)
     87 	interceptors.ChessInstance = interceptors.NewChess(db)
     88 	interceptors.BattleshipInstance = interceptors.NewBattleship(db)
     89 	interceptors.WWInstance = interceptors.NewWerewolf(db)
     90 
     91 	web.Start(db, host, port)
     92 	return nil
     93 }
     94 
     95 // Returns either or not the monero wallet balance matches the database balance
     96 func walletIsBalanced(db *database.DkfDB, client wallet1.Client) (balanced bool) {
     97 	resBalance, err := client.GetBalance(&wallet1.RequestGetBalance{})
     98 	if err != nil {
     99 		logrus.Error(err)
    100 		return false
    101 	}
    102 	walletBalance := database.Piconero(resBalance.Balance)
    103 
    104 	var diffInOut database.Piconero
    105 	if err := db.WithE(func(tx *database.DkfDB) error {
    106 		sumIn, err := tx.GetPokerXmrTransactionsSumIn()
    107 		if err != nil {
    108 			return err
    109 		}
    110 		sumOut, err := tx.GetPokerXmrTransactionsSumOut()
    111 		if err != nil {
    112 			return err
    113 		}
    114 		diffInOut = sumIn - sumOut
    115 		return nil
    116 	}); err != nil {
    117 		logrus.Error(err)
    118 		return false
    119 	}
    120 
    121 	return walletBalance == diffInOut
    122 }
    123 
    124 func isFirstUse(db *database.DkfDB) bool {
    125 	var count int64
    126 	db.DB().Model(database.User{}).Count(&count)
    127 	return count <= 0
    128 }
    129 
    130 func openBrowser(noBrowser bool, port int64) {
    131 	if noBrowser {
    132 		return
    133 	}
    134 	time.Sleep(1000 * time.Millisecond)
    135 	if err := open.Run(fmt.Sprintf("http://127.0.0.1:%d", port)); err != nil {
    136 		logrus.Error("failed to open browser : " + err.Error())
    137 	}
    138 }
    139 
    140 func runMigrations(db *database.DkfDB) {
    141 	logrus.Info("running migrations")
    142 	migrations := &migrate.AssetMigrationSource{
    143 		Asset: config.MigrationsFs.ReadFile,
    144 		AssetDir: func(path string) ([]string, error) {
    145 			dir, err := config.MigrationsFs.ReadDir(path)
    146 			out := make([]string, 0)
    147 			for _, d := range dir {
    148 				out = append(out, d.Name())
    149 			}
    150 			return out, err
    151 		},
    152 		Dir: "migrations",
    153 	}
    154 	db.DB().Exec("PRAGMA foreign_keys=OFF")
    155 	n, err := migrate.Exec(utils.Must(db.DB().DB()), "sqlite3", migrations, migrate.Up)
    156 	if err != nil {
    157 		panic(err)
    158 	}
    159 	db.DB().Exec("PRAGMA foreign_keys=ON")
    160 	logrus.Infof("applied %d migrations", n)
    161 }
    162 
    163 // Ensure the project folder is created properly
    164 func ensureProjectHome() {
    165 	config.Global.ProjectPath.Set(utils.MustGetDefaultProjectPath())
    166 	projectPath := config.Global.ProjectPath.Get()
    167 	if err := os.MkdirAll(projectPath, 0755); err != nil {
    168 		logrus.Fatal("Failed to create project folder", err)
    169 	}
    170 
    171 	config.Global.ProjectLocalsPath.Set(filepath.Join(projectPath, "locals"))
    172 	if err := os.MkdirAll(config.Global.ProjectLocalsPath.Get(), 0755); err != nil {
    173 		logrus.Fatal("Failed to create dkforest locals folder", err)
    174 	}
    175 
    176 	config.Global.ProjectHTMLPath.Set(filepath.Join(projectPath, "html"))
    177 	if err := os.MkdirAll(config.Global.ProjectHTMLPath.Get(), 0755); err != nil {
    178 		logrus.Fatal("Failed to create dkforest html folder", err)
    179 	}
    180 
    181 	config.Global.ProjectMemesPath.Set(filepath.Join(projectPath, "memes"))
    182 	if err := os.MkdirAll(config.Global.ProjectMemesPath.Get(), 0755); err != nil {
    183 		logrus.Fatal("Failed to create memes uploads folder", err)
    184 	}
    185 
    186 	config.Global.ProjectUploadsPath.Set(filepath.Join(projectPath, "uploads"))
    187 	if err := os.MkdirAll(config.Global.ProjectUploadsPath.Get(), 0755); err != nil {
    188 		logrus.Fatal("Failed to create dkforest uploads folder", err)
    189 	}
    190 
    191 	config.Global.ProjectFiledropPath.Set(filepath.Join(projectPath, "filedrop"))
    192 	if err := os.MkdirAll(config.Global.ProjectFiledropPath.Get(), 0755); err != nil {
    193 		logrus.Fatal("Failed to create dkforest filedrop folder", err)
    194 	}
    195 
    196 	// Contains files that we offer for download directly
    197 	config.Global.ProjectDownloadsPath.Set(filepath.Join(projectPath, "downloads"))
    198 	if err := os.MkdirAll(config.Global.ProjectDownloadsPath.Get(), 0755); err != nil {
    199 		logrus.Fatal("Failed to create dkforest downloads folder", err)
    200 	}
    201 }
    202 
    203 // LogFormatter ...
    204 type LogFormatter struct{}
    205 
    206 // Format ...
    207 func (f LogFormatter) Format(e *logrus.Entry) ([]byte, error) {
    208 	skip := 6
    209 	var fn string
    210 	var line int
    211 	for {
    212 		_, fn, line, _ = runtime.Caller(skip)
    213 		if !strings.Contains(fn, "/logrus/") || skip >= 10 {
    214 			break
    215 		}
    216 		skip++
    217 	}
    218 	var buffer bytes.Buffer
    219 	var level string
    220 	switch e.Level {
    221 	case logrus.DebugLevel:
    222 		level = color.Magenta("DEBU")
    223 	case logrus.InfoLevel:
    224 		level = color.Cyan("INFO")
    225 	case logrus.WarnLevel:
    226 		level = color.Yellow("WARN")
    227 	case logrus.ErrorLevel:
    228 		level = color.Red("ERRO")
    229 	case logrus.FatalLevel:
    230 		level = color.Red("FATA")
    231 	case logrus.PanicLevel:
    232 		level = color.Red("PANI")
    233 	}
    234 	buffer.WriteString(e.Time.Format("06/01/02 15:04:05"))
    235 	buffer.WriteString(" ")
    236 	buffer.WriteString(level)
    237 	buffer.WriteString(" ")
    238 	buffer.WriteString(color.Magenta("[" + strings.TrimSuffix(filepath.Base(fn), ".go") + ":" + strconv.Itoa(line) + "]"))
    239 	buffer.WriteString(" ")
    240 	buffer.WriteString(e.Message)
    241 	buffer.WriteString("\n")
    242 	return buffer.Bytes(), nil
    243 }
    244 
    245 // When the application starts, check that the wallet is balanced
    246 func xmrCheck(db *database.DkfDB) {
    247 	if config.IsFirstUse.IsTrue() {
    248 		return
    249 	}
    250 	logrus.Info("start xmr check")
    251 	walletClient := config.Xmr()
    252 	poker.Refund(db)
    253 	if !walletIsBalanced(db, walletClient) {
    254 		// TODO: automatically send transactions that are not processed
    255 		config.PokerWithdrawEnabled.Store(false)
    256 		logrus.Error("wallet is not balanced")
    257 		dutils.RootAdminNotify(db, "wallet is not balanced; poker withdraw disabled")
    258 	}
    259 	logrus.Info("done xmr check")
    260 }
    261 
    262 func xmrWatch(db *database.DkfDB) {
    263 	var once utils.Once
    264 	for {
    265 		select {
    266 		case <-once.After(5 * time.Second):
    267 		case <-time.After(1 * time.Minute):
    268 		}
    269 		transfers, err := config.Xmr().GetTransfers(&wallet1.RequestGetTransfers{In: true})
    270 		if err != nil {
    271 			continue
    272 		}
    273 		for _, transfer := range transfers.In {
    274 			if processPokerTransfer(db, transfer) {
    275 				continue
    276 			}
    277 		}
    278 	}
    279 }
    280 
    281 func processPokerTransfer(db *database.DkfDB, transfer *wallet1.Transfer) (found bool) {
    282 	var user database.User
    283 	pokerTransfer, err := db.GetPokerXmrTransaction(transfer.TxID)
    284 	if err != nil {
    285 		user, err = db.GetUserByPokerXmrSubAddress(transfer.Address)
    286 		if err != nil {
    287 			return false // Not a poker deposit address
    288 		}
    289 
    290 		// Create a new transaction and rotate user deposit address
    291 		if txErr := db.WithE(func(tx *database.DkfDB) error {
    292 			userID := user.ID
    293 			pokerTransfer, err = tx.CreatePokerXmrInTransaction(userID, transfer)
    294 			if err != nil {
    295 				return err
    296 			}
    297 			// Update user's xmr deposit address
    298 			res, err := config.Xmr().CreateAddress(&wallet1.RequestCreateAddress{})
    299 			if err != nil {
    300 				return err
    301 			}
    302 			if err := tx.SetPokerSubAddress(userID, res.Address); err != nil {
    303 				return err
    304 			}
    305 			return nil
    306 		}); txErr != nil {
    307 			logrus.Error(txErr)
    308 			return true
    309 		}
    310 	} else {
    311 		if pokerTransfer.Confirmations < 10 {
    312 			pokerTransfer.Confirmations = utils.MinInt(transfer.Confirmations, 10)
    313 			pokerTransfer.DoSave(db)
    314 		}
    315 		if pokerTransfer.Processed {
    316 			return true
    317 		}
    318 		user, _ = db.GetUserByID(pokerTransfer.UserID)
    319 	}
    320 
    321 	if !pokerTransfer.HasEnoughConfirmations() {
    322 		return true
    323 	}
    324 
    325 	// Increment user's xmr balance, and update transfer status
    326 	if txErr := db.WithE(func(tx *database.DkfDB) error {
    327 		if err := user.IncrXmrBalance(tx, pokerTransfer.Amount); err != nil {
    328 			return err
    329 		}
    330 		pokerTransfer.Processed = true
    331 		pokerTransfer.DoSave(tx)
    332 		return nil
    333 	}); txErr != nil {
    334 		logrus.Error(err)
    335 		return true
    336 	}
    337 
    338 	dutils.RootAdminNotify(db, fmt.Sprintf("new deposit %s xmr by %s", pokerTransfer.Amount.XmrStr(), user.Username))
    339 	return true
    340 }
    341 
    342 func cleanupDatabase(db *database.DkfDB) {
    343 	var once utils.Once
    344 	for {
    345 		select {
    346 		case <-once.After(5 * time.Second):
    347 		case <-time.After(1 * time.Hour):
    348 		}
    349 		start := time.Now()
    350 		db.DeleteOldSessions()
    351 		db.DeleteOldUploads()
    352 		db.DeleteOldChatMessages()
    353 		db.DeleteOldPrivateChatRooms()
    354 		db.DeleteOldCaptchaRequests()
    355 		db.DeleteOldAuditLogs()
    356 		db.DeleteOldSecurityLogs()
    357 		db.DeleteOldIgnoredUsers()
    358 		db.DeleteOldPmBlacklistedUsers()
    359 		db.DeleteOldPmWhitelistedUsers()
    360 		db.DeleteOldChatInboxMessages()
    361 		db.DeleteOldDownloads()
    362 		db.DeleteOldSessionNotifications()
    363 		db.DeleteOldUserLastKnownMessages()
    364 		logrus.Debugf("done cleaning database, took %s", time.Since(start))
    365 	}
    366 }
    367 
    368 func BuildProhibitedPasswords(c *cli.Context) error {
    369 	// TODO: Fix gormbulk to use new gorm lib
    370 	//fmt.Println("start")
    371 	//if !utils.FileExists("rockyou.txt") {
    372 	//	return errors.New("rockyou.txt not found")
    373 	//}
    374 	//
    375 	//ensureProjectHome()
    376 	//
    377 	//dbPath := filepath.Join(config.Global.ProjectPath.Get(), config.DbFileName)
    378 	//db := database.NewDkfDB(dbPath).DB()
    379 	//
    380 	//readFile, _ := os.Open("rockyou.txt")
    381 	//fileScanner := bufio.NewScanner(readFile)
    382 	//fileScanner.Split(bufio.ScanLines)
    383 	//var rows []interface{}
    384 	//for fileScanner.Scan() {
    385 	//	rows = append(rows, database.ProhibitedPassword{Password: fileScanner.Text()})
    386 	//}
    387 	//readFile.Close()
    388 	//if err := gormbulk.BulkInsert(db, rows, 10000); err != nil {
    389 	//	logrus.Error(err)
    390 	//}
    391 	return nil
    392 }