Browse Source

Use sqlx to simplify things

master
Trevor Slocum 5 years ago
parent
commit
54c43696ac
  1. 10
      Gopkg.lock
  2. 14
      anonircd.go
  3. 68
      channel.go
  4. 119
      client.go
  5. 246
      database.go
  6. 417
      server.go
  7. 27
      utilities.go
  8. 24
      vendor/github.com/jmoiron/sqlx/.gitignore
  9. 23
      vendor/github.com/jmoiron/sqlx/LICENSE
  10. 185
      vendor/github.com/jmoiron/sqlx/README.md
  11. 207
      vendor/github.com/jmoiron/sqlx/bind.go
  12. 12
      vendor/github.com/jmoiron/sqlx/doc.go
  13. 344
      vendor/github.com/jmoiron/sqlx/named.go
  14. 132
      vendor/github.com/jmoiron/sqlx/named_context.go
  15. 136
      vendor/github.com/jmoiron/sqlx/named_context_test.go
  16. 227
      vendor/github.com/jmoiron/sqlx/named_test.go
  17. 17
      vendor/github.com/jmoiron/sqlx/reflectx/README.md
  18. 422
      vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
  19. 905
      vendor/github.com/jmoiron/sqlx/reflectx/reflect_test.go
  20. 1039
      vendor/github.com/jmoiron/sqlx/sqlx.go
  21. 335
      vendor/github.com/jmoiron/sqlx/sqlx_context.go
  22. 1344
      vendor/github.com/jmoiron/sqlx/sqlx_context_test.go
  23. 1795
      vendor/github.com/jmoiron/sqlx/sqlx_test.go
  24. 5
      vendor/github.com/jmoiron/sqlx/types/README.md
  25. 172
      vendor/github.com/jmoiron/sqlx/types/types.go
  26. 127
      vendor/github.com/jmoiron/sqlx/types/types_test.go
  27. 61
      vendor/golang.org/x/net/internal/socket/zsys_darwin_arm64.go

10
Gopkg.lock generated

@ -19,6 +19,12 @@
revision = "96dc06278ce32a0e9d957d590bb987c81ee66407"
version = "v1.3.0"
[[projects]]
branch = "master"
name = "github.com/jmoiron/sqlx"
packages = [".","reflectx"]
revision = "99f3ad6d85ae53d0fecf788ab62d0e9734b3c117"
[[projects]]
name = "github.com/mattn/go-sqlite3"
packages = ["."]
@ -41,7 +47,7 @@
branch = "master"
name = "golang.org/x/net"
packages = ["context"]
revision = "a8b9294777976932365dabb6640cf1468d95c70f"
revision = "dc871a5d77e227f5bbf6545176ef3eeebf87e76e"
[[projects]]
branch = "v2"
@ -52,6 +58,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "da08912cf0f9aa88d93fa8fb8cf01a421f16b7977841c275f883c0c6821adcca"
inputs-digest = "2597d02f0d1ff0af313642458ae19f0dabc6e5464adc94013e82fa3285a75c4e"
solver-name = "gps-cdcl"
solver-version = 1

14
anonircd.go

@ -28,7 +28,8 @@ import (
"time"
"github.com/jessevdk/go-flags"
irc "gopkg.in/sorcix/irc.v2"
"github.com/pkg/errors"
"gopkg.in/sorcix/irc.v2"
)
var prefixAnonymous = irc.Prefix{"Anonymous", "Anon", "IRC"}
@ -45,11 +46,8 @@ const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const writebuffersize = 10
const (
PERMISSION_USER = 0
PERMISSION_SUPERADMIN = 1
PERMISSION_ADMIN = 2
PERMISSION_MODERATOR = 3
PERMISSION_VIP = 4
CHANNEL_LOBBY = "#"
CHANNEL_SERVER = "&"
)
var debugMode = false
@ -67,7 +65,7 @@ func main() {
_, err := flags.Parse(&opts)
if err != nil {
panic(err)
log.Panicf("%+v", errors.Wrap(err, "failed to parse flags"))
}
if opts.Debug > 0 {
@ -85,7 +83,7 @@ func main() {
s := NewServer(opts.ConfigFile)
err = s.loadConfig()
if err != nil {
panic(err)
log.Panicf("%+v", errors.Wrap(err, "failed to load configuration file"))
}
s.connectDatabase()
defer s.closeDatabase()

68
channel.go

@ -2,16 +2,19 @@ package main
import (
"fmt"
"sort"
"strings"
"sync"
"time"
"gopkg.in/sorcix/irc.v2"
)
type Channel struct {
Entity
clients *sync.Map
logs []*ChannelLog
logs map[int64]*ChannelLog
topic string
topictime int64
@ -23,6 +26,7 @@ type ChannelLog struct {
Timestamp int64
Client string
IP string
Account int
Action string
Message string
}
@ -30,11 +34,11 @@ type ChannelLog struct {
const CHANNEL_LOGS_PER_PAGE = 25
func (cl *ChannelLog) Identifier(index int) string {
return fmt.Sprintf("%03d%02d", index+1, cl.Timestamp%100)
return fmt.Sprintf("%03d%02d", index, cl.Timestamp%100)
}
func (cl *ChannelLog) Print(index int, channel string) string {
return strings.TrimSpace(fmt.Sprintf("%s %s %5s %4s %s", time.Unix(0, cl.Timestamp).Format(time.Stamp), channel, cl.Identifier(index), cl.Action, cl.Message))
return strings.TrimSpace(fmt.Sprintf("%s %s %s %4s %s", time.Unix(0, cl.Timestamp).Format(time.Stamp), channel, cl.Identifier(index), cl.Action, cl.Message))
}
func NewChannel(identifier string) *Channel {
@ -42,6 +46,7 @@ func NewChannel(identifier string) *Channel {
c.Initialize(ENTITY_CHANNEL, identifier)
c.clients = new(sync.Map)
c.logs = make(map[int64]*ChannelLog)
return c
}
@ -50,13 +55,14 @@ func (c *Channel) Log(client *Client, action string, message string) {
c.Lock()
defer c.Unlock()
// TODO: Log size limiting, max capacity will be 998 entries
// TODO: Log size limiting, max capacity will be 999 entries
// Log hash of IP address which is used later when connecting/joining
c.logs = append(c.logs, &ChannelLog{Timestamp: time.Now().UTC().UnixNano(), Client: client.identifier, IP: client.ip, Action: action, Message: message})
nano := time.Now().UTC().UnixNano()
c.logs[nano] = &ChannelLog{Timestamp: nano, Client: client.identifier, IP: client.iphash, Account: client.account, Action: action, Message: message}
}
func (c *Channel) RevealLog(page int) []string {
func (c *Channel) RevealLog(page int, full bool) []string {
c.RLock()
defer c.RUnlock()
@ -66,14 +72,30 @@ func (c *Channel) RevealLog(page int) []string {
var ls []string
logsRemain := false
j := 0
for i, l := range c.logs {
var nanos int64arr
for n := range c.logs {
nanos = append(nanos, n)
}
sort.Sort(nanos)
// To perform the opertion you want
var l *ChannelLog
var ok bool
for i, nano := range nanos {
if l, ok = c.logs[nano]; !ok {
continue
}
if page == -1 || i >= (CHANNEL_LOGS_PER_PAGE*(page-1)) {
if page > -1 && j == CHANNEL_LOGS_PER_PAGE {
logsRemain = true
break
if full || (l.Action != irc.JOIN && l.Action != irc.PART) {
if page > -1 && j == CHANNEL_LOGS_PER_PAGE {
logsRemain = true
break
}
ls = append(ls, l.Print(i, c.identifier))
j++
}
ls = append(ls, l.Print(i, c.identifier))
j++
}
}
@ -96,21 +118,31 @@ func (c *Channel) RevealLog(page int) []string {
return ls
}
func (c *Channel) RevealHash(identifier string) string {
func (c *Channel) RevealInfo(identifier string) (string, int) {
if len(identifier) != 5 {
return ""
return "", 0
}
c.RLock()
defer c.RUnlock()
for i, l := range c.logs {
if l.Identifier(i) == identifier {
return l.IP
var nanos int64arr
for n := range c.logs {
nanos = append(nanos, n)
}
sort.Sort(nanos)
var l *ChannelLog
var ok bool
for i, nano := range nanos {
if l, ok = c.logs[nano]; !ok {
continue
} else if l.Identifier(i) == identifier {
return l.IP, l.Account
}
}
return ""
return "", 0
}
func (c *Channel) HasClient(client string) bool {

119
client.go

@ -1,16 +1,21 @@
package main
import (
"log"
"net"
"sync"
"strings"
"fmt"
irc "gopkg.in/sorcix/irc.v2"
)
type Client struct {
Entity
ip string
iphash string
ssl bool
nick string
@ -39,9 +44,7 @@ func NewClient(identifier string, conn net.Conn, ssl bool) *Client {
return nil
}
c.ip = generateHash(ip)
// TODO: Check bans, return nil
c.iphash = generateHash(ip)
c.ssl = ssl
c.nick = "*"
c.conn = conn
@ -53,8 +56,21 @@ func NewClient(identifier string, conn net.Conn, ssl bool) *Client {
return c
}
func (c *Client) getAccount() (*DBAccount, error) {
if c.account == 0 {
return nil, nil
}
acc, err := db.Account(c.account)
if err != nil {
return nil, err
}
return &acc, nil
}
func (c *Client) registered() bool {
// TODO
// TODO get account and check if it is valid
return c.account > 0
}
@ -90,6 +106,95 @@ func (c *Client) sendNotice(message string) {
c.sendMessage("*** " + message)
}
func (c *Client) accessDenied() {
c.sendNotice("Access denied")
func (c *Client) accessDenied(permissionRequired int) {
ex := ""
if permissionRequired > PERMISSION_CLIENT {
ex = fmt.Sprintf(", that command is available to %ss only", strings.ToLower(permissionLabels[permissionRequired]))
if permissionRequired == PERMISSION_REGISTERED {
ex += " - Reply HELP for more info (see REGISTER and IDENTIFY)"
}
}
c.sendNotice("Access denied" + ex)
}
func (c *Client) identify(username string, password string) bool {
accountid, err := db.Auth(username, password)
if err != nil {
log.Panicf("%+v", err)
}
account, err := db.Account(accountid)
if err != nil {
log.Panicf("%+v", err)
} else if account.ID == 0 {
return false
}
c.account = accountid
return true
}
func (c *Client) getPermission(channel string) int {
if c.account == 0 {
return PERMISSION_CLIENT
}
p, err := db.GetPermission(c.account, channel)
if err != nil {
log.Panicf("%+v", err)
}
return p.Permission
}
func (c *Client) globalPermission() int {
return c.getPermission("&")
}
func (c *Client) canUse(command string, channel string) bool {
command = strings.ToUpper(command)
req := c.permissionRequired(command)
globalPermission := c.globalPermission()
if globalPermission >= req {
return true
} else if containsString(serverCommands, command) {
return false
}
return c.getPermission(channel) >= req
}
func (c *Client) permissionRequired(command string) int {
command = strings.ToUpper(command)
for permissionRequired, commands := range commandRestrictions {
for _, cmd := range commands {
if cmd == command {
return permissionRequired
}
}
}
return 0
}
func (c *Client) isBanned(channel string) (bool, string) {
b, err := db.BanAddr(c.iphash, channel)
if err != nil {
log.Panicf("%+v", err)
}
if b.Channel == "" && c.account > 0 {
b, err = db.BanAccount(c.account, channel)
if err != nil {
log.Panicf("%+v", err)
}
}
if b.Channel != "" {
return true, b.Reason
}
return false, ""
}

246
database.go

@ -1,21 +1,23 @@
package main
import (
"database/sql"
"encoding/base64"
"fmt"
"log"
"strconv"
"strings"
"github.com/gorilla/securecookie"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
)
const DATABASE_VERSION = 1
var ErrAccountExists = errors.New("account exists")
var ErrChannelExists = errors.New("channel exists")
var ErrAccountExists = errors.New("account already exists")
var ErrChannelExists = errors.New("channel already exists")
var ErrChannelDoesNotExist = errors.New("channel does not exist")
var tables = map[string][]string{
"meta": {
@ -41,10 +43,15 @@ var tables = map[string][]string{
"`expires` INTEGER NULL",
"`reason` TEXT NULL"}}
const (
BAN_TYPE_ADDRESS = 1
BAN_TYPE_ACCOUNT = 2
)
type DBAccount struct {
ID int
Username string
Permission int
ID int
Username string
Password string
}
type DBChannel struct {
@ -60,6 +67,12 @@ type DBPermission struct {
Permission int
}
type DBMode struct {
Channel string
Mode string
Value string
}
type DBBan struct {
Channel string
Type int
@ -69,12 +82,12 @@ type DBBan struct {
}
type Database struct {
db *sql.DB
db *sqlx.DB
}
func (d *Database) Connect(driver string, dataSource string) error {
var err error
d.db, err = sql.Open(driver, dataSource)
d.db, err = sqlx.Connect(driver, dataSource)
if err != nil {
return errors.Wrapf(err, "failed to connect to %s database", driver)
}
@ -110,7 +123,7 @@ func (d *Database) CreateTables() error {
func (d *Database) Migrate() error {
rows, err := d.db.Query("SELECT `value` FROM meta WHERE `key`=? LIMIT 1", "version")
if err != nil {
if p(err) {
return errors.Wrap(err, "failed to fetch database version")
}
@ -129,7 +142,7 @@ func (d *Database) Migrate() error {
}
if version == -1 {
panic("Unable to migrate database: database version unknown")
log.Panic("Unable to migrate database: database version unknown")
} else if version == 0 {
_, err := d.db.Exec("UPDATE meta SET `value`=? WHERE `key`=?", strconv.Itoa(DATABASE_VERSION), "version")
if err != nil {
@ -143,23 +156,26 @@ func (d *Database) Migrate() error {
}
func (d *Database) Initialize() error {
username := ""
err := d.db.QueryRow("SELECT username FROM accounts").Scan(&username)
if err == sql.ErrNoRows {
err := d.AddAccount("admin", "password")
if err != nil {
return errors.Wrap(err, "failed to create first account")
}
a, err := db.Account(1)
if err != nil {
return errors.Wrap(err, "failed to initialize")
}
ac := &DBChannel{Channel: "&", Topic: "Secret Area of VIP Quality"}
d.AddChannel(1, ac)
if a.ID > 0 {
return nil // Admin account exists
}
uc := &DBChannel{Channel: "#", Topic: "Welcome to AnonIRC"}
d.AddChannel(1, uc)
} else if err != nil {
return errors.Wrap(err, "failed to check for first account")
err = d.AddAccount("admin", "password")
if err != nil {
return errors.Wrap(err, "failed to create initial administrator account")
}
ac := &DBChannel{Channel: CHANNEL_SERVER, Topic: "Secret Area of VIP Quality"}
d.AddChannel(1, ac)
uc := &DBChannel{Channel: CHANNEL_LOBBY, Topic: "Welcome to AnonIRC"}
d.AddChannel(1, uc)
return nil
}
@ -173,37 +189,21 @@ func (d *Database) Close() error {
// Accounts
func (d *Database) Account(id int) (*DBAccount, error) {
rows, err := d.db.Query("SELECT id, username FROM accounts WHERE id=?", id)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch account")
}
var a *DBAccount
for rows.Next() {
a = new(DBAccount)
err = rows.Scan(&a.ID, &a.Username)
if err != nil {
return nil, errors.Wrap(err, "failed to scan account")
}
func (d *Database) Account(id int) (DBAccount, error) {
a := DBAccount{}
err := d.db.Get(&a, "SELECT * FROM accounts WHERE id=? LIMIT 1", id)
if p(err) {
return a, errors.Wrap(err, "failed to fetch account")
}
return a, nil
}
func (d *Database) AccountU(username string) (*DBAccount, error) {
rows, err := d.db.Query("SELECT id, username FROM accounts WHERE username=?", generateHash(username))
if err != nil {
return nil, errors.Wrap(err, "failed to fetch account by username")
}
var a *DBAccount
for rows.Next() {
a = new(DBAccount)
err = rows.Scan(&a.ID, &a.Username)
if err != nil {
return nil, errors.Wrap(err, "failed to scan account")
}
func (d *Database) AccountU(username string) (DBAccount, error) {
a := DBAccount{}
err := d.db.Get(&a, "SELECT * FROM accounts WHERE username=? LIMIT 1", generateHash(username))
if p(err) {
return a, errors.Wrap(err, "failed to fetch account by username")
}
return a, nil
@ -212,20 +212,13 @@ func (d *Database) AccountU(username string) (*DBAccount, error) {
// TODO: Lockout on too many failed attempts
func (d *Database) Auth(username string, password string) (int, error) {
// TODO: Salt in config
rows, err := d.db.Query("SELECT id FROM accounts WHERE username=? AND password=?", generateHash(username), generateHash(username+"-"+password))
if err != nil {
a := DBAccount{}
err := d.db.Get(&a, "SELECT * FROM accounts WHERE username=? AND password=? LIMIT 1", generateHash(username), generateHash(username+"-"+password))
if p(err) {
return 0, errors.Wrap(err, "failed to authenticate account")
}
accountid := 0
for rows.Next() {
err = rows.Scan(&accountid)
if err != nil {
return 0, errors.Wrap(err, "failed to authenticate account")
}
}
return accountid, nil
return a.ID, nil
}
func (d *Database) GenerateToken() string {
@ -236,7 +229,7 @@ func (d *Database) AddAccount(username string, password string) error {
ex, err := d.AccountU(username)
if err != nil {
return errors.Wrap(err, "failed to search for existing account while adding account")
} else if ex != nil {
} else if ex.ID > 0 {
return ErrAccountExists
}
@ -252,7 +245,7 @@ func (d *Database) SetUsername(accountid int, username string, password string)
ex, err := d.AccountU(username)
if err != nil {
return errors.Wrap(err, "failed to search for existing account while setting username")
} else if ex != nil {
} else if ex.ID > 0 {
return ErrAccountExists
}
@ -275,37 +268,21 @@ func (d *Database) SetPassword(accountid int, username string, password string)
// Channels
func (d *Database) ChannelID(id int) (*DBChannel, error) {
rows, err := d.db.Query("SELECT channel, topic FROM channels WHERE id=?", id)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch channel")
}
var c *DBChannel
for rows.Next() {
c = new(DBChannel)
err = rows.Scan(&c.Channel, &c.Topic)
if err != nil {
return nil, errors.Wrap(err, "failed to scan channel")
}
func (d *Database) ChannelID(id int) (DBChannel, error) {
c := DBChannel{}
err := d.db.Get(&c, "SELECT * FROM channels WHERE id=? LIMIT 1", id)
if p(err) {
return c, errors.Wrap(err, "failed to fetch channel")
}
return c, nil
}
func (d *Database) Channel(channel string) (*DBChannel, error) {
rows, err := d.db.Query("SELECT channel, topic FROM channels WHERE channel=?", generateHash(channel))
if err != nil {
return nil, errors.Wrap(err, "failed to fetch channel by key")
}
var c *DBChannel
for rows.Next() {
c = new(DBChannel)
err = rows.Scan(&c.Channel, &c.Topic)
if err != nil {
return nil, errors.Wrap(err, "failed to scan channel")
}
func (d *Database) Channel(channel string) (DBChannel, error) {
c := DBChannel{}
err := d.db.Get(&c, "SELECT * FROM channels WHERE channel=? LIMIT 1", generateHash(channel))
if p(err) {
return c, errors.Wrap(err, "failed to fetch channel by key")
}
return c, nil
@ -315,7 +292,7 @@ func (d *Database) AddChannel(accountid int, channel *DBChannel) error {
ex, err := d.Channel(channel.Channel)
if err != nil {
return errors.Wrap(err, "failed to search for existing channel while adding channel")
} else if ex != nil {
} else if ex.Channel != "" {
return ErrChannelExists
}
@ -333,51 +310,51 @@ func (d *Database) AddChannel(accountid int, channel *DBChannel) error {
return nil
}
func (d *Database) GetPermission(accountid int, channel string) (int, error) {
rows, err := d.db.Query("SELECT permission FROM permissions WHERE account=? AND channel=?", accountid, generateHash(channel))
if err != nil {
return 0, errors.Wrap(err, "failed to authenticate account")
}
permission := PERMISSION_USER
for rows.Next() {
err = rows.Scan(&permission)
if err != nil {
return 0, errors.Wrap(err, "failed to authenticate account")
}
// Permissions
func (d *Database) GetPermission(accountid int, channel string) (DBPermission, error) {
dbp := DBPermission{}
// Return REGISTERED by default
dbp.Permission = PERMISSION_REGISTERED
err := d.db.Get(&dbp, "SELECT * FROM permissions WHERE account=? AND channel=? LIMIT 1", accountid, generateHash(channel))
if p(err) {
return dbp, errors.Wrap(err, "failed to fetch permission")
}
return permission, nil
return dbp, nil
}
func (d *Database) SetPermission(accountid int, channel string, permission int) error {
acc, err := d.Account(accountid)
if err != nil {
panic(err)
} else if acc == nil {
log.Panicf("%+v", err)
} else if acc.ID == 0 {
return nil
}
ch, err := d.Channel(channel)
if err != nil {
return errors.Wrap(err, "failed to fetch channel while setting permission")
} else if ch == nil {
} else if ch.Channel == "" {
return nil
}
chh := generateHash(channel)
rows, err := d.db.Query("SELECT permission FROM permissions WHERE account=? AND channel=?", accountid, chh)
dbp, err := d.GetPermission(accountid, chh)
if err != nil {
return errors.Wrap(err, "failed to set permission")
}
if !rows.Next() {
_, err = d.db.Exec("INSERT INTO permissions (channel, account, permission) VALUES (?, ?, ?)", chh, accountid, permission)
if dbp.Channel != "" {
_, err = d.db.Exec("UPDATE permissions SET permission=? WHERE account=? AND channel=?", permission, accountid, chh)
if err != nil {
return errors.Wrap(err, "failed to set permission")
}
} else {
_, err = d.db.Exec("UPDATE permissions SET permission=? WHERE account=? AND channel=?", permission, accountid, chh)
_, err = d.db.Exec("INSERT INTO permissions (channel, account, permission) VALUES (?, ?, ?)", chh, accountid, permission)
if err != nil {
return errors.Wrap(err, "failed to set permission")
}
@ -385,3 +362,56 @@ func (d *Database) SetPermission(accountid int, channel string, permission int)
return nil
}
// Bans
func (d *Database) Ban(banid int) (DBBan, error) {
b := DBBan{}
err := d.db.Get(&b, "SELECT * FROM bans WHERE id=? LIMIT 1", banid)
if p(err) {
return b, errors.Wrap(err, "failed to fetch ban")
}
return b, nil
}
func (d *Database) BanAddr(addrhash string, channel string) (DBBan, error) {
b := DBBan{}
err := d.db.Get(&b, "SELECT * FROM bans WHERE channel=? AND `type`=? AND target=?", generateHash(channel), BAN_TYPE_ADDRESS, addrhash)
if p(err) {
return b, errors.Wrap(err, "failed to fetch ban")
}
return b, nil
}
func (d *Database) BanAccount(accountid int, channel string) (DBBan, error) {
b := DBBan{}
err := d.db.Get(&b, "SELECT * FROM bans WHERE channel=? AND `type`=? AND target=?", generateHash(channel), BAN_TYPE_ACCOUNT, accountid)
if p(err) {
return b, errors.Wrap(err, "failed to fetch ban")
}
return b, nil
}
func (d *Database) AddBan(b DBBan) error {
var err error
// Channel-specific (not server-wide)
if b.Channel != "&" {
ex, err := d.Channel(b.Channel)
if err != nil {
return errors.Wrap(err, "failed to search for existing ban while adding ban")
} else if ex.Channel == "" {
return ErrChannelDoesNotExist
}
}
_, err = d.db.Exec("INSERT INTO bans (`channel`, `type`, `target`, `expires`, `reason`) VALUES (?, ?, ?, ?, ?, ?)", b.Channel, b.Type, b.Target, b.Expires, b.Reason)
if p(err) {
return errors.Wrap(err, "failed to add ban")
}
return nil
}

417
server.go

@ -10,17 +10,16 @@ import (
"net"
"os"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"sort"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
irc "gopkg.in/sorcix/irc.v2"
"gopkg.in/sorcix/irc.v2"
)
const (
@ -30,11 +29,13 @@ const (
// User commands
COMMAND_REGISTER = "REGISTER"
COMMAND_IDENTIFY = "IDENTIFY"
COMMAND_TOKEN = "TOKEN"
COMMAND_USERNAME = "USERNAME"
COMMAND_PASSWORD = "PASSWORD"
// Channel/server commands
COMMAND_FOUND = "FOUND"
COMMAND_DROP = "DROP"
COMMAND_GRANT = "GRANT"
COMMAND_REVEAL = "REVEAL"
COMMAND_KICK = "KICK"
@ -47,6 +48,81 @@ const (
COMMAND_UPGRADE = "UPGRADE"
)
var serverCommands = []string{COMMAND_KILL, COMMAND_STATS, COMMAND_REHASH, COMMAND_UPGRADE}
const (
PERMISSION_CLIENT = 0
PERMISSION_REGISTERED = 1
PERMISSION_VIP = 2
PERMISSION_MODERATOR = 3
PERMISSION_ADMIN = 4
PERMISSION_SUPERADMIN = 5
)
var permissionLabels = map[int]string{
PERMISSION_CLIENT: "Client",
PERMISSION_REGISTERED: "Registered",
PERMISSION_VIP: "VIP",
PERMISSION_MODERATOR: "Moderator",
PERMISSION_ADMIN: "Administrator",
PERMISSION_SUPERADMIN: "Super Administrator",
}
var commandRestrictions = map[int][]string{
PERMISSION_REGISTERED: {COMMAND_TOKEN, COMMAND_USERNAME, COMMAND_PASSWORD, COMMAND_FOUND},
PERMISSION_MODERATOR: {COMMAND_REVEAL, COMMAND_KICK, COMMAND_BAN},
PERMISSION_ADMIN: {COMMAND_GRANT},
PERMISSION_SUPERADMIN: {COMMAND_DROP, COMMAND_KILL, COMMAND_STATS, COMMAND_REHASH, COMMAND_UPGRADE}}
var helpDuration = "Duration can be 0 to never expire, or e.g. 30m, 1h, 2d, 3w"
var commandUsage = map[string][]string{
COMMAND_HELP: {"[command]",
"Print info regarding all commands or a specific command"},
COMMAND_INFO: {"[channel]",
"When a channel is specified, prints info including whether it is registered",
"Without a channel, server info is printed"},
COMMAND_REGISTER: {"<username> <password>",
"Create an account, allowing you to found channels and moderate existing channels",
"See IDENTIFY, FOUND, GRANT"},
COMMAND_IDENTIFY: {"[username] <password>",
"Identify to a previously registered account",
"If username is omitted, it will be replaced with your current nick",
"Note that you may automatically identify when connecting by specifying a server password of your username and password separated by a colon - Example: admin:hunter2"},
COMMAND_TOKEN: {"<channel>",
"Returns a token which can be used by channel administrators to grant special access to your account"},
COMMAND_USERNAME: {"<username> <password> <new username> <confirm new username>",
"Change your username"},
COMMAND_PASSWORD: {"<username> <password> <new password> <confirm new password>",
"Change your password"},
COMMAND_FOUND: {"<channel>",
"Register a channel"},
COMMAND_GRANT: {"<channel> [account] [updated access]",
"When an account token isn't specified, all permissions are listed",
"View or update a user's access level by specifying their account token",
"To remove an account, set their access level to User"},
COMMAND_REVEAL: {"<channel> [page] [full]",
"Print channel log, allowing KICK/BAN to be used",
fmt.Sprintf("Results start at page 1, %d per page", CHANNEL_LOGS_PER_PAGE),
"All log entries are returned when viewing page -1",
"By default joins and parts are hidden, use 'full' to show them"},
COMMAND_KICK: {"<channel> <5 digit log number> [reason]",
"Kick a user from a channel"},
COMMAND_BAN: {"<channel> <5 digit log number> <duration> [reason]",
"Kick and ban a user from a channel",
helpDuration},
COMMAND_DROP: {"<channel> <confirm channel>",
"Delete all channel data, allowing it to be FOUNDed again"},
COMMAND_KILL: {"<channel> <5 digit log number> <duration> [reason]",
"Disconnect and ban a user from the server",
helpDuration},
COMMAND_STATS: {"",
"Print the current number of clients and channels"},
COMMAND_REHASH: {"",
"Reload the server configuration"},
COMMAND_UPGRADE: {"",
"Upgrade the server without disconnecting clients"},
}
type Config struct {
Salt string
DBDriver string
@ -59,7 +135,6 @@ type Server struct {
config *Config
configfile string
created int64
db *Database
clients *sync.Map
channels *sync.Map
odyssey *os.File
@ -71,12 +146,13 @@ type Server struct {
*sync.RWMutex
}
var db = &Database{}
func NewServer(configfile string) *Server {
s := &Server{}
s.config = &Config{}
s.configfile = configfile
s.created = time.Now().Unix()
s.db = new(Database)
s.clients = new(sync.Map)
s.channels = new(sync.Map)
s.odysseymutex = new(sync.RWMutex)
@ -150,6 +226,17 @@ func (s *Server) getClient(client string) *Client {
func (s *Server) getClients(channel string) map[string]*Client {
clients := make(map[string]*Client)
if channel == "" {
s.clients.Range(func(k, v interface{}) bool {
cl := s.getClient(k.(string))
if cl != nil {
clients[cl.identifier] = cl
}
return true
})
return clients
}
ch := s.getChannel(channel)
if ch == nil {
return clients
@ -157,7 +244,7 @@ func (s *Server) getClients(channel string) map[string]*Client {
ch.clients.Range(func(k, v interface{}) bool {
cl := s.getClient(k.(string))
if channel == "" || cl != nil {
if cl != nil {
clients[cl.identifier] = cl
}
return true
@ -177,23 +264,15 @@ func (s *Server) clientCount() int {
}
func (s *Server) revealClient(channel string, identifier string) *Client {
if len(identifier) != 5 {
riphash, raccount := s.revealClientInfo(channel, identifier)
if riphash == "" && raccount == 0 {
log.Println("hash not found")
return nil
}
ch := s.getChannel(channel)
if ch == nil {
return nil
}
rip := ch.RevealHash(identifier)
if rip == "" {
return nil
}
cls := s.getClients(ch.identifier)
log.Println("have hash")
cls := s.getClients("")
for _, rcl := range cls {
if rcl.ip == rip {
if rcl.iphash == riphash || (rcl.account > 0 && rcl.account == raccount) {
return rcl
}
}
@ -201,6 +280,19 @@ func (s *Server) revealClient(channel string, identifier string) *Client {
return nil
}
func (s *Server) revealClientInfo(channel string, identifier string) (string, int) {
if len(identifier) != 5 {
return "", 0
}
ch := s.getChannel(channel)
if ch == nil {
return "", 0
}
return ch.RevealInfo(identifier)
}
func (s *Server) inChannel(channel string, client string) bool {
ch := s.getChannel(channel)
if ch != nil {
@ -224,8 +316,8 @@ func (s *Server) joinChannel(channel string, client string) {
if len(channel) == 0 {
return
} else if channel[0] == '&' {
if s.globalPermission(cl) == PERMISSION_USER {
cl.accessDenied()
if cl.globalPermission() < PERMISSION_VIP {
cl.accessDenied(0)
return
}
} else if channel[0] != '#' {
@ -241,6 +333,16 @@ func (s *Server) joinChannel(channel string, client string) {
return
}
banned, reason := cl.isBanned(channel)
if banned {
ex := ""
if reason != "" {
ex = ". Reason: " + reason
}
cl.sendNotice("Unable to join " + channel + ": You are banned" + ex)
return
}
ch.clients.Store(client, s.clientsInChannel(channel, client)+1)
cl.write(&irc.Message{cl.getPrefix(), irc.JOIN, []string{channel}})
ch.Log(cl, irc.JOIN, "")
@ -272,7 +374,7 @@ func (s *Server) partAllChannels(client string, reason string) {
}
}
func (s *Server) revealChannelLog(channel string, client string, page int) {
func (s *Server) revealChannelLog(channel string, client string, page int, full bool) {
cl := s.getClient(client)
if cl == nil {
return
@ -288,7 +390,7 @@ func (s *Server) revealChannelLog(channel string, client string, page int) {
return
}
r := ch.RevealLog(page)
r := ch.RevealLog(page, full)
for _, rev := range r {
cl.sendMessage(rev)
}
@ -443,11 +545,11 @@ func (s *Server) handleTopic(channel string, client string, topic string) {
return
}
chp, err := s.db.GetPermission(cl.account, channel)
chp, err := db.GetPermission(cl.account, channel)
if err != nil {
panic(err)
} else if ch.hasMode("t") && (cl.account == 0 || chp == PERMISSION_USER) {
cl.accessDenied()
log.Panicf("%+v", err)
} else if ch.hasMode("t") && (chp.Permission < PERMISSION_VIP) {
cl.accessDenied(PERMISSION_VIP)
return
}
@ -485,9 +587,9 @@ func (s *Server) handleMode(c *Client, params []string) {
// Send channel creation time
c.writeMessage(strings.Join([]string{"329", c.nick, params[0], fmt.Sprintf("%d", int32(ch.created))}, " "), []string{})
} else if len(params) > 1 && len(params[1]) > 0 && (params[1][0] == '+' || params[1][0] == '-') {
if !s.hasPermission(c, irc.MODE, params[0]) {
if !c.canUse(irc.MODE, params[0]) {
// TODO: Send proper mode denied message
c.accessDenied()
c.accessDenied(c.permissionRequired(irc.MODE))
return
}
@ -582,55 +684,11 @@ func (s *Server) handleMode(c *Client, params []string) {
}
func (s *Server) buildUsage(cl *Client, command string) map[string][]string {
helpDuration := "Duration can be 0 to never expire, or e.g. 30m, 1h, 2d, 3w"
var commandUsage = map[string][]string{
COMMAND_HELP: {"[command]",
"Print info regarding all commands or a specific command"},
COMMAND_INFO: {"[channel]",
"Print info such as whether a channel is registered",
"If channel is omitted, server info is printed instead"},
COMMAND_REGISTER: {"<username> <password>",
"Create an account, allowing you to found channels and moderate existing channels (with permission)",
"See IDENTIFY"},
COMMAND_IDENTIFY: {"[username] <password>",
"Identify to a previously registered account",
"If username is omitted, it will be replaced with your current nick",
"Note that you may identify when connecting by sending the following server password:",
"Your username and password: <username>:<password>",
"Or your username and token: <username>:<token>",
"E.g. admin:hunter2"},
COMMAND_USERNAME: {"<username> <password> <new username> <confirm new username>",
"Change your username"},
COMMAND_PASSWORD: {"<username> <password> <new password> <confirm new password>",
"Change your password"},
COMMAND_FOUND: {"<channel>",
"Register a channel"},
COMMAND_REVEAL: {"<channel> [page]",
"Print channel log, allowing KICK/BAN to be used",
fmt.Sprintf("Results start at page 1, %d per page", CHANNEL_LOGS_PER_PAGE),
"All log entries are returned when viewing page -1"},
COMMAND_KICK: {"<channel> <5 digit log number> [reason]",
"Kick a user from a channel"},
COMMAND_BAN: {"<channel> <5 digit log number> <duration> [reason]",
"Kick and ban a user from a channel",
helpDuration},
COMMAND_KILL: {"<channel> <5 digit log number> <duration> [reason]",
"Disconnect and ban a user from the server",
helpDuration},
COMMAND_STATS: {"",
"Print the current number of clients and channels"},
COMMAND_REHASH: {"",
"Reload the server configuration"},
COMMAND_UPGRADE: {"",
"Upgrade the server without disconnecting clients"},
}
u := map[string][]string{}
command = strings.ToUpper(command)
for cmd, usage := range commandUsage {
if command == COMMAND_HELP || cmd == command {
if s.hasPermission(cl, cmd, "") {
if cl.canUse(cmd, "") {
u[cmd] = usage
}
}
@ -668,6 +726,15 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
var err error
command = strings.ToUpper(command)
ch := ""
if len(params) > 0 {
ch = params[0]
}
if !cl.canUse(command, ch) {
cl.accessDenied(cl.permissionRequired(command))
return
}
switch command {
case COMMAND_HELP:
cmd := command
@ -677,7 +744,8 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
s.sendUsage(cl, cmd)
return
case COMMAND_INFO:
cl.sendMessage("AnonIRCd https://github.com/sageru-6ch/anonircd")
// TODO: when channel is supplied, send whether it is registered and show a notice that it is dropping soon if no super admins have logged in in X days
cl.sendMessage("Server info: AnonIRCd https://github.com/sageru-6ch/anonircd")
return
case COMMAND_REGISTER:
if len(params) == 0 {
@ -699,12 +767,24 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
password = params[1]
}
authSuccess := s.identify(cl, username, password)
authSuccess := cl.identify(username, password)
if authSuccess {
cl.sendNotice("Identified successfully")
if s.globalPermission(cl) != PERMISSION_USER {
s.joinChannel("&", cl.identifier)
if cl.globalPermission() >= PERMISSION_VIP {
s.joinChannel(CHANNEL_SERVER, cl.identifier)
}
for clch := range s.getChannels(cl.identifier) {
banned, br := cl.isBanned(clch)
if banned {
reason := "Banned"
if br != "" {
reason += ": " + br
}
s.partChannel(clch, cl.identifier, reason)
return
}
}
} else {
cl.sendNotice("Failed to identify, incorrect username/password")
@ -714,7 +794,7 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
cl.sendError("You must identify before using that command")
}
if len(params) == 0 || len(params) > 4 {
if len(params) == 0 || len(params) < 4 {
s.sendUsage(cl, command)
return
}
@ -725,9 +805,9 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
}
// TODO: Alphanumeric username
accid, err := s.db.Auth(params[0], params[1])
accid, err := db.Auth(params[0], params[1])
if err != nil {
panic(err)
log.Panicf("%+v", err)
}
if accid == 0 {
@ -735,17 +815,13 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
return
}
err = s.db.SetUsername(accid, params[2], params[1])
err = db.SetUsername(accid, params[2], params[1])
if err != nil {
panic(err)
log.Panicf("%+v", err)
}
cl.sendMessage("Username changed successfully")
case COMMAND_PASSWORD:
if cl.account == 0 {
cl.sendError("You must identify before using that command")
}
if len(params) == 0 || len(params) > 4 {
if len(params) == 0 || len(params) < 4 {
s.sendUsage(cl, command)
return
}
@ -755,9 +831,9 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
return
}
accid, err := s.db.Auth(params[0], params[1])
accid, err := db.Auth(params[0], params[1])
if err != nil {
panic(err)
log.Panicf("%+v", err)
}
if accid == 0 {
@ -765,9 +841,9 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
return
}
err = s.db.SetPassword(accid, params[0], params[2])
err = db.SetPassword(accid, params[0], params[2])
if err != nil {
panic(err)
log.Panicf("%+v", err)
}
cl.sendMessage("Password changed successfully")
case COMMAND_REVEAL:
@ -783,11 +859,6 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
return
}
if !s.hasPermission(cl, COMMAND_REVEAL, params[0]) {
cl.accessDenied()
return
}
page := 1
if len(params) > 1 {
page, err = strconv.Atoi(params[1])
@ -797,7 +868,14 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
}
}
s.revealChannelLog(params[0], cl.identifier, page)
full := false
if len(params) > 2 {
if strings.ToLower(params[2]) == "full" {
full = true
}
}
s.revealChannelLog(params[0], cl.identifier, page, full)
case COMMAND_KICK:
if len(params) < 2 {
s.sendUsage(cl, command)
@ -810,11 +888,6 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
return
}
if !s.hasPermission(cl, COMMAND_KICK, params[0]) {
cl.accessDenied()
return
}
rcl := s.revealClient(params[0], params[1])
if rcl == nil {
cl.sendError("Unable to kick, client not found or no longer connected")
@ -839,30 +912,22 @@ func (s *Server) handleUserCommand(client string, command string, params []strin
return
}
if !s.hasPermission(cl, COMMAND_BAN, params[0]) {
cl.accessDenied()
return
}
rcl := s.revealClient(params[0], params[1])
if rcl == nil {
cl.sendError("Unable to ban, client not found or no longer connected")
return
}