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 }