Updated golang ekeletn for review
This commit is contained in:
parent
0df93b8f80
commit
bafe01a091
392
skelly.go
392
skelly.go
@ -7,170 +7,290 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IRC color & control codes
|
||||||
|
const (
|
||||||
|
bold = "\x02"
|
||||||
|
italic = "\x1D"
|
||||||
|
underline = "\x1F"
|
||||||
|
reverse = "\x16"
|
||||||
|
reset = "\x0f"
|
||||||
|
white = "00"
|
||||||
|
black = "01"
|
||||||
|
blue = "02"
|
||||||
|
green = "03"
|
||||||
|
red = "04"
|
||||||
|
brown = "05"
|
||||||
|
purple = "06"
|
||||||
|
orange = "07"
|
||||||
|
yellow = "08"
|
||||||
|
lightGreen = "09"
|
||||||
|
cyan = "10"
|
||||||
|
lightCyan = "11"
|
||||||
|
lightBlue = "12"
|
||||||
|
pink = "13"
|
||||||
|
grey = "14"
|
||||||
|
lightGrey = "15"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Connection settings
|
||||||
server string
|
server string
|
||||||
port string
|
port int
|
||||||
useSSL bool
|
|
||||||
channel string
|
channel string
|
||||||
key string
|
key string
|
||||||
|
password string
|
||||||
|
ipv4 bool
|
||||||
|
ipv6 bool
|
||||||
|
vhost string
|
||||||
|
|
||||||
|
// SSL settings
|
||||||
|
useSSL bool
|
||||||
|
sslVerify bool
|
||||||
|
sslCert string
|
||||||
|
sslPass string
|
||||||
|
|
||||||
|
// Bot settings
|
||||||
|
nick string
|
||||||
|
user string
|
||||||
|
real string
|
||||||
|
nickserv string
|
||||||
|
operserv string
|
||||||
|
mode string
|
||||||
|
flood int
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&server, "server", "", "The IRC server address (e.g., 1.2.3.4)")
|
flag.StringVar(&server, "server", "", "The IRC server address.")
|
||||||
flag.StringVar(&port, "port", "6667", "The port of the IRC server (e.g., 6667)")
|
flag.IntVar(&port, "port", 6667, "The port number for the IRC server.")
|
||||||
flag.BoolVar(&useSSL, "ssl", false, "Whether to use SSL or not")
|
flag.StringVar(&channel, "channel", "", "The IRC channel to join.")
|
||||||
flag.StringVar(&channel, "channel", "", "The IRC channel to join (e.g., #channelName)")
|
flag.StringVar(&key, "key", "", "The key (password) for the IRC channel, if required.")
|
||||||
flag.StringVar(&key, "key", "", "The IRC channel key")
|
flag.StringVar(&password, "password", "", "The password for the IRC server.")
|
||||||
|
flag.BoolVar(&ipv4, "v4", false, "Use IPv4 for the connection.")
|
||||||
|
flag.BoolVar(&ipv6, "v6", false, "Use IPv6 for the connection.")
|
||||||
|
flag.StringVar(&vhost, "vhost", "", "The VHOST to use for connection.")
|
||||||
|
flag.BoolVar(&useSSL, "ssl", false, "Use SSL for the connection.")
|
||||||
|
flag.BoolVar(&sslVerify, "ssl-verify", false, "Verify SSL certificates.")
|
||||||
|
flag.StringVar(&sslCert, "ssl-cert", "", "The SSL certificate to use for the connection.")
|
||||||
|
flag.StringVar(&sslPass, "ssl-pass", "", "The SSL certificate password.")
|
||||||
|
flag.StringVar(&nick, "nick", "skelly", "The nickname to use for the bot.")
|
||||||
|
flag.StringVar(&user, "user", "skelly", "The username to use for the bot.")
|
||||||
|
flag.StringVar(&real, "real", "Development Bot", "The realname to use for the bot.")
|
||||||
|
flag.StringVar(&mode, "mode", "+B", "The mode to set on the bot's nickname.")
|
||||||
|
flag.StringVar(&nickserv, "nickserv", "", "The password for the bot's nickname to be identified with NickServ.")
|
||||||
|
flag.StringVar(&operserv, "operserv", "", "The password for the bot's nickname to be identified with OperServ.")
|
||||||
|
flag.IntVar(&flood, "flood", 3, "Delay between command usage.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func logfmt(option string, message string) string {
|
||||||
nickname = "[dev]skelly"
|
switch option {
|
||||||
username = "golang"
|
case "DEBUG":
|
||||||
realname = "IRC Bot Skeleton in Golang"
|
return fmt.Sprintf("\033[95m%s\033[0m [\033[95mDEBUG\033[0m] %s", getnow(), message)
|
||||||
)
|
case "ERROR":
|
||||||
|
return fmt.Sprintf("\033[95m%s\033[0m [\033[31mERROR\033[0m] %s", getnow(), message)
|
||||||
func checkArgs() {
|
case "SEND":
|
||||||
if server == "" || channel == "" {
|
return fmt.Sprintf("\033[95m%s\033[0m [\033[92mSEND\033[0m] %s", getnow(), message)
|
||||||
log.Fatal("Both server and channel arguments are required.")
|
case "RECV":
|
||||||
} else if channel[0] != '#' {
|
return fmt.Sprintf("\033[95m%s\033[0m [\033[96mRECV\033[0m] %s", getnow(), message)
|
||||||
channel = "#" + channel // Using # on the commandline requires escaping, lets just make it optional
|
|
||||||
}
|
}
|
||||||
num, err := strconv.Atoi(port)
|
return fmt.Sprintf("\033[95m%s\033[0m [\033[95mDEBUG\033[0m] %s", getnow(), message) // This should never happen
|
||||||
if err != nil {
|
}
|
||||||
log.Fatal("Invalid port number.")
|
|
||||||
} else if num < 1 || num > 65535 {
|
func color(msg string, foreground string, background string) string {
|
||||||
log.Fatal("Port number must be between 1 and 65535.")
|
if background != "" {
|
||||||
|
return fmt.Sprintf("\x03%s,%s%s%s", foreground, background, msg, reset)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("\x03%s%s%s", foreground, msg, reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bot struct {
|
||||||
|
nickname string
|
||||||
|
username string
|
||||||
|
realname string
|
||||||
|
conn net.Conn
|
||||||
|
reader *bufio.Reader
|
||||||
|
writer *bufio.Writer
|
||||||
|
last time.Time
|
||||||
|
slow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Skeleton() *Bot {
|
||||||
|
return &Bot{
|
||||||
|
nickname: "skeleton",
|
||||||
|
username: "skelly",
|
||||||
|
realname: "Development Bot",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func (bot *Bot) Connect() error {
|
||||||
checkArgs()
|
address := fmt.Sprintf("%s:%d", server, port)
|
||||||
|
|
||||||
fullServer := fmt.Sprintf("%s:%s", server, port)
|
var networkType string
|
||||||
|
switch {
|
||||||
var conn net.Conn
|
case ipv4:
|
||||||
var err error
|
networkType = "tcp4"
|
||||||
|
case ipv6:
|
||||||
if useSSL {
|
networkType = "tcp6"
|
||||||
conn, err = tls.Dial("tcp", fullServer, &tls.Config{InsecureSkipVerify: true})
|
|
||||||
} else {
|
|
||||||
conn, err = net.Dial("tcp", fullServer)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to connect: %v", err)
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
messageChannel := make(chan string, 100) // for received IRC messages
|
|
||||||
sendChannel := make(chan string, 100) // for messages to send to the IRC server
|
|
||||||
quit := make(chan struct{})
|
|
||||||
|
|
||||||
timeoutDuration := 300 * time.Second
|
|
||||||
timeoutTimer := time.NewTimer(timeoutDuration)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
reader := bufio.NewReader(conn)
|
|
||||||
for {
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error reading from server:", err)
|
|
||||||
conn.Close()
|
|
||||||
close(quit)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case messageChannel <- line:
|
|
||||||
timeoutTimer.Reset(timeoutDuration)
|
|
||||||
case <-quit:
|
|
||||||
return
|
|
||||||
case <-timeoutTimer.C:
|
|
||||||
log.Println("No data received for 300 seconds. Reconnecting...")
|
|
||||||
conn.Close()
|
|
||||||
close(quit)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case message := <-sendChannel:
|
|
||||||
data := []byte(message)
|
|
||||||
if len(data) > 510 {
|
|
||||||
data = data[:510]
|
|
||||||
}
|
|
||||||
_, err := conn.Write(append(data, '\r', '\n'))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error writing to server:", err)
|
|
||||||
conn.Close()
|
|
||||||
close(quit)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Initial handshake
|
|
||||||
sendChannel <- fmt.Sprintf("NICK %s", nickname)
|
|
||||||
sendChannel <- fmt.Sprintf("USER %s 0 * :%s", username, realname)
|
|
||||||
|
|
||||||
for {
|
|
||||||
dataHandler(<-messageChannel, sendChannel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventPrivate(nick, ident, msg string) {
|
|
||||||
fmt.Println("Private message from", nick, ":", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventMessage(nick, ident, channel, msg string) {
|
|
||||||
fmt.Println("Channel message from", nick, ":", msg)
|
|
||||||
if ident == "acidvegas!~stillfree@most.dangerous.motherfuck" {
|
|
||||||
args := strings.Split(msg, " ")
|
|
||||||
switch args[0] {
|
|
||||||
case "!masscan":
|
|
||||||
fmt.Println("The value is a")
|
|
||||||
case "!nmap":
|
|
||||||
fmt.Println("The value is b")
|
|
||||||
case "!httpx":
|
|
||||||
fmt.Println("The value is c")
|
|
||||||
case "!nuclei":
|
|
||||||
fmt.Println("The value is c")
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unknown value")
|
networkType = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialer net.Dialer
|
||||||
|
|
||||||
|
if vhost != "" {
|
||||||
|
localAddr, err := net.ResolveTCPAddr(networkType, vhost+":0")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to resolve local address: %w", err)
|
||||||
|
}
|
||||||
|
dialer.LocalAddr = localAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if useSSL {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: !sslVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslCert != "" {
|
||||||
|
var cert tls.Certificate
|
||||||
|
cert, err = tls.LoadX509KeyPair(sslCert, sslPass)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load SSL certificate: %w", err)
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.conn, err = tls.DialWithDialer(&dialer, networkType, address, tlsConfig)
|
||||||
|
} else {
|
||||||
|
bot.conn, err = dialer.Dial(networkType, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to dial: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.reader = bufio.NewReader(bot.conn)
|
||||||
|
bot.writer = bufio.NewWriter(bot.conn)
|
||||||
|
|
||||||
|
if password != "" {
|
||||||
|
bot.raw("PASS " + password)
|
||||||
|
}
|
||||||
|
bot.raw(fmt.Sprintf("USER %s 0 * :%s", user, real))
|
||||||
|
bot.raw("NICK " + nick)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) raw(data string) {
|
||||||
|
if bot.writer != nil {
|
||||||
|
bot.writer.WriteString(data + "\r\n")
|
||||||
|
bot.writer.Flush()
|
||||||
|
if strings.Split(data, " ")[0] == "PONG" {
|
||||||
|
fmt.Println(logfmt("SEND", "\033[93m"+data+"\033[0m"))
|
||||||
|
} else {
|
||||||
|
fmt.Println(logfmt("SEND", data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dataHandler(data string, sendChannel chan<- string) {
|
func (bot *Bot) sendMsg(target string, msg string) {
|
||||||
fmt.Println(data)
|
bot.raw(fmt.Sprintf("PRIVMSG %s :%s", target, msg))
|
||||||
parts := strings.Split(data, " ")
|
}
|
||||||
|
|
||||||
if parts[0] == "PING" {
|
func (bot *Bot) handle(data string) {
|
||||||
sendChannel <- fmt.Sprintf("PONG %s", parts[1])
|
parts := strings.Fields(data)
|
||||||
} else if parts[1] == "001" { // RPL_WELCOME
|
|
||||||
time.Sleep(5 * time.Second) // JOIN channel delay after connection, required for many networks with flood protection / mitigation
|
if len(parts) < 2 {
|
||||||
sendChannel <- fmt.Sprintf("JOIN %s %s", channel, key)
|
|
||||||
} else if parts[1] == "KICK" {
|
|
||||||
return
|
return
|
||||||
} else if len(parts) > 3 && parts[1] == "PRIVMSG" {
|
}
|
||||||
nick := strings.Split(parts[0], "!")[0][1:]
|
|
||||||
ident := strings.Split(parts[0], ":")[1]
|
if parts[0] != "PING" {
|
||||||
|
parts[1] = "\033[38;5;141m" + parts[1] + "\033[0m"
|
||||||
|
}
|
||||||
|
coloredData := strings.Join(parts, " ")
|
||||||
|
fmt.Println(logfmt("RECV", coloredData))
|
||||||
|
|
||||||
|
parts = strings.Fields(data)
|
||||||
|
if parts[0] == "PING" {
|
||||||
|
bot.raw("PONG " + parts[1])
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
command := parts[1]
|
||||||
|
switch command {
|
||||||
|
case "001": // RPL_WELCOME
|
||||||
|
bot.raw("MODE " + nick + " " + mode)
|
||||||
|
|
||||||
|
if nickserv != "" {
|
||||||
|
bot.raw("PRIVMSG NickServ :IDENTIFY " + nickserv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if operserv != "" {
|
||||||
|
bot.raw("OPER " + nick + " " + operserv)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
if key != "" {
|
||||||
|
bot.raw("JOIN " + channel + " " + key)
|
||||||
|
} else {
|
||||||
|
bot.raw("JOIN " + channel)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case "PRIVMSG":
|
||||||
|
bot.eventPrivMsg(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getnow() string {
|
||||||
|
return time.Now().Format("03:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) eventPrivMsg(data string) {
|
||||||
|
parts := strings.Split(data, " ")
|
||||||
|
ident := strings.TrimPrefix(parts[0], ":")
|
||||||
|
nick := strings.Split(ident, "!")[0]
|
||||||
target := parts[2]
|
target := parts[2]
|
||||||
msg := strings.Join(parts[3:], " ")[1:]
|
msg := strings.Join(parts[3:], " ")[1:]
|
||||||
if target == nickname { // Private Messages
|
|
||||||
eventPrivate(nick, ident, msg)
|
if target == bot.nickname {
|
||||||
} else if target == channel { // Channel Messages
|
// Private message handling
|
||||||
eventMessage(nick, ident, channel, msg)
|
} else if strings.HasPrefix(target, "#") {
|
||||||
|
if target == channel {
|
||||||
|
if msg == "!test" {
|
||||||
|
bot.sendMsg(channel, nick+": Test successful!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func main() {
|
||||||
|
for {
|
||||||
|
fmt.Printf("\033[90m%s\033[0m [\033[95mDEBUG\033[0m] Connecting to %s:%d and joining %s\n", getnow(), server, port, channel)
|
||||||
|
|
||||||
|
bot := Skeleton()
|
||||||
|
err := bot.Connect()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("\033[90m%s\033[0m [\033[31mERROR\033[0m]\033[93m Failed to connect to server! Retrying in 15 seconds... \033[90m(%v)\033[0m", getnow(), err)
|
||||||
|
} else {
|
||||||
|
for {
|
||||||
|
line, _, err := bot.reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("\033[90m%s\033[0m [\033[31mERROR\033[0m]\033[93m Lost connection to server! Retrying in 15 seconds... \033[90m(%v)\033[0m", getnow(), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
bot.handle(string(line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bot.conn != nil {
|
||||||
|
bot.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user