// ---------------------------------------------------------------------------- func main() { commands, appConfig, err := getConfig() if err != nil { log.Fatal(err) } bot, err := tgbotapi.NewBotAPI(appConfig.token) if err != nil { log.Fatal(err) } log.Printf("Authorized on bot account: @%s", bot.Self.UserName) tgbotConfig := tgbotapi.NewUpdate(0) tgbotConfig.Timeout = appConfig.botTimeout botUpdatesChan, err := bot.GetUpdatesChan(tgbotConfig) if err != nil { log.Fatal(err) } users := NewUsers(appConfig) messageSignal := make(chan BotMessage, MessagesQueueSize) vacuumTicker := time.Tick(SecondsForOldUsersBeforeVacuum * time.Second) saveToBDTicker := make(<-chan time.Time) exitSignal := make(chan struct{}) systemExitSignal := make(chan os.Signal) signal.Notify(systemExitSignal, os.Interrupt, os.Kill) if appConfig.persistentUsers { saveToBDTicker = time.Tick(SecondsForAutoSaveUsersToDB * time.Second) } var cacheTTL *cache.MemoryTTL if appConfig.cache > 0 { cacheTTL = cache.NewMemoryWithTTL(time.Duration(appConfig.cache) * time.Second) cacheTTL.StartGC(time.Duration(appConfig.cache) * time.Second * 2) } // all /shell2telegram sub-commands handlers internalCommands := map[string]func(Ctx) string{ "stat": cmdShell2telegramStat, "ban": cmdShell2telegramBan, "search": cmdShell2telegramSearch, "desc": cmdShell2telegramDesc, "rm": cmdShell2telegramRm, "exit": cmdShell2telegramExit, "version": cmdShell2telegramVersion, "broadcast_to_root": cmdShell2telegramBroadcastToRoot, "message_to_user": cmdShell2telegramMessageToUser, } doExit := false for !doExit { select { case telegramUpdate := <-botUpdatesChan: var messageCmd, messageArgs string allUserMessage := telegramUpdate.Message.Text if len(allUserMessage) > 0 && allUserMessage[0] == '/' { messageCmd, messageArgs = splitStringHalfBySpace(allUserMessage) } else { messageCmd, messageArgs = "/:plain_text", allUserMessage } allowPlainText := false if _, ok := commands["/:plain_text"]; ok { allowPlainText = true } replayMsg := "" if len(messageCmd) > 0 && (messageCmd != "/:plain_text" || allowPlainText) { users.AddNew(telegramUpdate.Message) userID := telegramUpdate.Message.From.ID allowExec := appConfig.allowAll || users.IsAuthorized(userID) ctx := Ctx{ appConfig: &appConfig, users: &users, commands: commands, userID: userID, allowExec: allowExec, messageCmd: messageCmd, messageArgs: messageArgs, messageSignal: messageSignal, chatID: telegramUpdate.Message.Chat.ID, exitSignal: exitSignal, cacheTTL: cacheTTL, } switch { // commands ................................. case !appConfig.isPublicBot && (messageCmd == "/auth" || messageCmd == "/authroot"): replayMsg = cmdAuth(ctx) case messageCmd == "/help": replayMsg = cmdHelp(ctx) case messageCmd == "/shell2telegram" && users.IsRoot(userID): messageSubCmd := "" messageSubCmd, messageArgs = splitStringHalfBySpace(messageArgs) ctx.messageArgs = messageArgs if cmdHandler, ok := internalCommands[messageSubCmd]; ok { replayMsg = cmdHandler(ctx) } else { replayMsg = "Sub-command not found" } case allowExec && (allowPlainText && messageCmd == "/:plain_text" || messageCmd[0] == '/'): cmdUser(ctx) } // switch for commands if appConfig.logCommands { log.Printf("%s: %s", users.String(userID), allUserMessage) } sendMessage(messageSignal, telegramUpdate.Message.Chat.ID, []byte(replayMsg), false) } case botMessage := <-messageSignal: switch { case botMessage.messageType == msgIsText && !stringIsEmpty(botMessage.message): messageConfig := tgbotapi.NewMessage(botMessage.chatID, botMessage.message) if botMessage.isMarkdown { messageConfig.ParseMode = tgbotapi.ModeMarkdown } _, err = bot.Send(messageConfig) case botMessage.messageType == msgIsPhoto && len(botMessage.photo) > 0: bytesPhoto := tgbotapi.FileBytes{Name: botMessage.fileName, Bytes: botMessage.photo} _, err = bot.Send(tgbotapi.NewPhotoUpload(botMessage.chatID, bytesPhoto)) } if err != nil { log.Print("Bot send message error: ", err) } case <-saveToBDTicker: users.SaveToDB(appConfig.usersDB) case <-vacuumTicker: users.ClearOldUsers() case <-systemExitSignal: go func() { exitSignal <- struct{}{} }() case <-exitSignal: if appConfig.persistentUsers { users.needSaveDB = true users.SaveToDB(appConfig.usersDB) } doExit = true } } }
// exec shell commands with text to STDIN func execShell(shellCmd, input string, varsNames []string, userID, chatID int, userName, userDisplayName string, cacheTTL *cache.MemoryTTL) (result []byte) { cacheKey := shellCmd + "/" + input if cacheTTL != nil { cacheData, err := cacheTTL.Get(cacheKey) if err != cache.ErrNotFound && err != nil { log.Print(err) } else if err == nil { // cache hit return cacheData.([]byte) } } shell, params := "sh", []string{"-c", shellCmd} if runtime.GOOS == "windows" { shell, params = "cmd", []string{"/C", shellCmd} } osExecCommand := exec.Command(shell, params...) osExecCommand.Stderr = os.Stderr // copy variables from parent process for _, envRaw := range os.Environ() { osExecCommand.Env = append(osExecCommand.Env, envRaw) } if input != "" { if len(varsNames) > 0 { // set user input to shell vars arguments := regexp.MustCompile(`\s+`).Split(input, len(varsNames)) for i, arg := range arguments { osExecCommand.Env = append(osExecCommand.Env, fmt.Sprintf("%s=%s", varsNames[i], arg)) } } else { // write user input to STDIN stdin, err := osExecCommand.StdinPipe() if err == nil { io.WriteString(stdin, input) stdin.Close() } else { log.Print("get STDIN error: ", err) } } } // set S2T_* env vars s2tVariables := [...]struct{ name, value string }{ {"S2T_LOGIN", userName}, {"S2T_USERID", strconv.Itoa(userID)}, {"S2T_USERNAME", userDisplayName}, {"S2T_CHATID", strconv.Itoa(userID)}, } for _, row := range s2tVariables { osExecCommand.Env = append(osExecCommand.Env, fmt.Sprintf("%s=%s", row.name, row.value)) } shellOut, err := osExecCommand.Output() if err != nil { log.Print("exec error: ", err) result = []byte(fmt.Sprintf("exec error: %s", err)) } else { result = shellOut } if cacheTTL != nil { err := cacheTTL.Set(cacheKey, result) if err != nil { log.Print(err) } } return result }