Add option to hex encode the source IP of a client and use it as the ident, closes #24

This commit is contained in:
Ken-Håvard Lieng 2018-08-10 20:24:29 +02:00
parent e2c6cedc27
commit c975c5d120
17 changed files with 163 additions and 41 deletions

File diff suppressed because one or more lines are too long

View File

@ -29,14 +29,20 @@ class Connect extends Component {
this.setState({ showOptionals: !this.state.showOptionals }); this.setState({ showOptionals: !this.state.showOptionals });
}; };
renderOptionals = () => ( renderOptionals = () => {
const { hexIP } = this.props;
return (
<div> <div>
<TextInput name="username" placeholder="Username" /> {!hexIP && [
{this.renderError('username')} <TextInput name="username" placeholder="Username" />,
this.renderError('username')
]}
<TextInput type="password" name="password" placeholder="Password" /> <TextInput type="password" name="password" placeholder="Password" />
<TextInput name="realname" placeholder="Realname" /> <TextInput name="realname" placeholder="Realname" />
</div> </div>
); );
};
renderError = name => { renderError = name => {
const { touched, errors } = this.props; const { touched, errors } = this.props;

View File

@ -1,13 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import Connect from 'components/pages/Connect'; import Connect from 'components/pages/Connect';
import { getConnectDefaults } from 'state/app'; import { getConnectDefaults, getApp } from 'state/app';
import { join } from 'state/channels'; import { join } from 'state/channels';
import { connect as connectServer } from 'state/servers'; import { connect as connectServer } from 'state/servers';
import { select } from 'state/tab'; import { select } from 'state/tab';
const mapState = createStructuredSelector({ const mapState = createStructuredSelector({
defaults: getConnectDefaults defaults: getConnectDefaults,
hexIP: state => getApp(state).hexIP
}); });
const mapDispatch = { const mapDispatch = {

View File

@ -1,6 +1,6 @@
import Cookie from 'js-cookie'; import Cookie from 'js-cookie';
import { socket as socketActions } from 'state/actions'; import { socket as socketActions } from 'state/actions';
import { getWrapWidth, setConnectDefaults } from 'state/app'; import { getWrapWidth, setConnectDefaults, appSet } from 'state/app';
import { addMessages } from 'state/messages'; import { addMessages } from 'state/messages';
import { select, updateSelection } from 'state/tab'; import { select, updateSelection } from 'state/tab';
import { find } from 'utils'; import { find } from 'utils';
@ -11,6 +11,7 @@ export default function initialState({ store }) {
const env = JSON.parse(document.getElementById('env').innerHTML); const env = JSON.parse(document.getElementById('env').innerHTML);
store.dispatch(setConnectDefaults(env.defaults)); store.dispatch(setConnectDefaults(env.defaults));
store.dispatch(appSet('hexIP', env.hexIP));
if (env.servers) { if (env.servers) {
store.dispatch({ store.dispatch({

View File

@ -21,7 +21,8 @@ const initialState = {
password: false, password: false,
readonly: false, readonly: false,
showDetails: false showDetails: false
} },
hexIP: false
}; };
export default createReducer(initialState, { export default createReducer(initialState, {

View File

@ -103,6 +103,7 @@ func init() {
viper.BindPFlag("port", rootCmd.Flags().Lookup("port")) viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
viper.BindPFlag("dev", rootCmd.Flags().Lookup("dev")) viper.BindPFlag("dev", rootCmd.Flags().Lookup("dev"))
viper.SetDefault("hexIP", false)
viper.SetDefault("verify_client_certificates", true) viper.SetDefault("verify_client_certificates", true)
} }

View File

@ -1,4 +1,6 @@
port = 80 port = 80
# Hex encode the users IP and use it as the ident
hexIP = false
verify_certificates = true verify_certificates = true
# Defaults for the client connect form # Defaults for the client connect form

View File

@ -25,6 +25,7 @@ type indexData struct {
Defaults connectDefaults Defaults connectDefaults
Servers []Server Servers []Server
Channels []*storage.Channel Channels []*storage.Channel
HexIP bool
// Users in the selected channel // Users in the selected channel
Users *Userlist Users *Userlist
@ -34,7 +35,9 @@ type indexData struct {
} }
func getIndexData(r *http.Request, state *State) *indexData { func getIndexData(r *http.Request, state *State) *indexData {
data := indexData{} data := indexData{
HexIP: viper.GetBool("hexIP"),
}
data.Defaults = connectDefaults{ data.Defaults = connectDefaults{
Name: viper.GetString("defaults.name"), Name: viper.GetString("defaults.name"),

View File

@ -97,6 +97,8 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
} }
in.Delim(']') in.Delim(']')
} }
case "hexIP":
out.HexIP = bool(in.Bool())
case "users": case "users":
if in.IsNull() { if in.IsNull() {
in.Skip() in.Skip()
@ -187,6 +189,16 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
out.RawByte(']') out.RawByte(']')
} }
} }
if in.HexIP {
const prefix string = ",\"hexIP\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.HexIP))
}
if in.Users != nil { if in.Users != nil {
const prefix string = ",\"users\":" const prefix string = ",\"users\":"
if first { if first {

13
server/ip.go Normal file
View File

@ -0,0 +1,13 @@
package server
import "net"
func addrToIPBytes(addr net.Addr) []byte {
ip := addr.(*net.TCPAddr).IP
if ipv4 := ip.To4(); ipv4 != nil {
return ipv4
}
return ip
}

View File

@ -2,6 +2,7 @@ package server
import ( import (
"crypto/tls" "crypto/tls"
"encoding/hex"
"net" "net"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -26,7 +27,7 @@ func createNickInUseHandler(i *irc.Client, state *State) func(string) string {
} }
} }
func connectIRC(server *storage.Server, state *State) *irc.Client { func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client {
i := irc.NewClient(server.Nick, server.Username) i := irc.NewClient(server.Nick, server.Username)
i.TLS = server.TLS i.TLS = server.TLS
i.Realname = server.Realname i.Realname = server.Realname
@ -37,9 +38,12 @@ func connectIRC(server *storage.Server, state *State) *irc.Client {
address = net.JoinHostPort(server.Host, server.Port) address = net.JoinHostPort(server.Host, server.Port)
} }
if i.Username == "" { if viper.GetBool("hexIP") {
i.Username = hex.EncodeToString(srcIP)
} else if i.Username == "" {
i.Username = server.Nick i.Username = server.Nick
} }
if i.Realname == "" { if i.Realname == "" {
i.Realname = server.Nick i.Realname = server.Nick
} }

View File

@ -93,7 +93,7 @@ func (d *Dispatch) loadUser(user *storage.User) {
} }
for _, server := range servers { for _, server := range servers {
i := connectIRC(server, state) i := connectIRC(server, state, user.GetLastIP())
var joining []string var joining []string
for _, channel := range channels { for _, channel := range channels {

View File

@ -2,6 +2,7 @@ package server
import ( import (
"log" "log"
"net"
"net/http" "net/http"
"strings" "strings"
@ -12,24 +13,24 @@ import (
type wsHandler struct { type wsHandler struct {
ws *wsConn ws *wsConn
state *State state *State
addr string addr net.Addr
handlers map[string]func([]byte) handlers map[string]func([]byte)
} }
func newWSHandler(conn *websocket.Conn, state *State, r *http.Request) *wsHandler { func newWSHandler(conn *websocket.Conn, state *State, r *http.Request) *wsHandler {
var address string
if r.Header.Get("X-Forwarded-For") != "" {
address = r.Header.Get("X-Forwarded-For")
} else {
address = conn.RemoteAddr().String()
}
h := &wsHandler{ h := &wsHandler{
ws: newWSConn(conn), ws: newWSConn(conn),
state: state, state: state,
addr: address, addr: conn.RemoteAddr(),
} }
if r.Header.Get("X-Forwarded-For") != "" {
ip := net.ParseIP(r.Header.Get("X-Forwarded-For"))
if ip != nil {
h.addr.(*net.TCPAddr).IP = ip
}
}
h.init(r) h.init(r)
h.initHandlers() h.initHandlers()
return h return h
@ -44,7 +45,7 @@ func (h *wsHandler) run() {
req, ok := <-h.ws.in req, ok := <-h.ws.in
if !ok { if !ok {
if h.state != nil { if h.state != nil {
h.state.deleteWS(h.addr) h.state.deleteWS(h.addr.String())
} }
return return
} }
@ -60,7 +61,8 @@ func (h *wsHandler) dispatchRequest(req WSRequest) {
} }
func (h *wsHandler) init(r *http.Request) { func (h *wsHandler) init(r *http.Request) {
h.state.setWS(h.addr, h.ws) h.state.setWS(h.addr.String(), h.ws)
h.state.user.SetLastIP(addrToIPBytes(h.addr))
log.Println(h.addr, "[State] User ID:", h.state.user.ID, "|", log.Println(h.addr, "[State] User ID:", h.state.user.ID, "|",
h.state.numIRC(), "IRC connections |", h.state.numIRC(), "IRC connections |",
@ -98,7 +100,7 @@ func (h *wsHandler) connect(b []byte) {
if _, ok := h.state.getIRC(data.Host); !ok { if _, ok := h.state.getIRC(data.Host); !ok {
log.Println(h.addr, "[IRC] Add server", data.Host) log.Println(h.addr, "[IRC] Add server", data.Host)
connectIRC(data.Server, h.state) connectIRC(data.Server, h.state, addrToIPBytes(h.addr))
go h.state.user.AddServer(data.Server) go h.state.user.AddServer(data.Server)
} else { } else {

View File

@ -54,13 +54,11 @@ func (s *BoltStore) GetUsers() ([]*storage.User, error) {
s.db.View(func(tx *bolt.Tx) error { s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketUsers) b := tx.Bucket(bucketUsers)
return b.ForEach(func(k, _ []byte) error { return b.ForEach(func(k, v []byte) error {
id := idFromBytes(k)
user := storage.User{ user := storage.User{
ID: id,
IDBytes: make([]byte, 8), IDBytes: make([]byte, 8),
Username: strconv.FormatUint(id, 10),
} }
user.Unmarshal(v)
copy(user.IDBytes, k) copy(user.IDBytes, k)
users = append(users, &user) users = append(users, &user)
@ -76,7 +74,10 @@ func (s *BoltStore) SaveUser(user *storage.User) error {
return s.db.Batch(func(tx *bolt.Tx) error { return s.db.Batch(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketUsers) b := tx.Bucket(bucketUsers)
if user.ID == 0 {
user.ID, _ = b.NextSequence() user.ID, _ = b.NextSequence()
user.IDBytes = idToBytes(user.ID)
}
user.Username = strconv.FormatUint(user.ID, 10) user.Username = strconv.FormatUint(user.ID, 10)
data, err := user.Marshal(nil) data, err := user.Marshal(nil)
@ -84,7 +85,6 @@ func (s *BoltStore) SaveUser(user *storage.User) error {
return err return err
} }
user.IDBytes = idToBytes(user.ID)
return b.Put(user.IDBytes, data) return b.Put(user.IDBytes, data)
}) })
} }

View File

@ -1,6 +1,7 @@
struct User { struct User {
ID uint64 ID uint64
Username string Username string
lastIP []byte
} }
struct Server { struct Server {

View File

@ -29,6 +29,21 @@ func (d *User) Size() (s uint64) {
} }
s += l s += l
} }
{
l := uint64(len(d.lastIP))
{
t := l
for t >= 0x80 {
t >>= 7
s++
}
s++
}
s += l
}
s += 8 s += 8
return return
} }
@ -67,6 +82,25 @@ func (d *User) Marshal(buf []byte) ([]byte, error) {
copy(buf[i+8:], d.Username) copy(buf[i+8:], d.Username)
i += l i += l
} }
{
l := uint64(len(d.lastIP))
{
t := uint64(l)
for t >= 0x80 {
buf[i+8] = byte(t) | 0x80
t >>= 7
i++
}
buf[i+8] = byte(t)
i++
}
copy(buf[i+8:], d.lastIP)
i += l
}
return buf[:i+8], nil return buf[:i+8], nil
} }
@ -98,6 +132,31 @@ func (d *User) Unmarshal(buf []byte) (uint64, error) {
d.Username = string(buf[i+8 : i+8+l]) d.Username = string(buf[i+8 : i+8+l])
i += l i += l
} }
{
l := uint64(0)
{
bs := uint8(7)
t := uint64(buf[i+8] & 0x7F)
for buf[i+8]&0x80 == 0x80 {
i++
t |= uint64(buf[i+8]&0x7F) << bs
bs += 7
}
i++
l = t
}
if uint64(cap(d.lastIP)) >= l {
d.lastIP = d.lastIP[:l]
} else {
d.lastIP = make([]byte, l)
}
copy(d.lastIP, buf[i+8:])
i += l
}
return i + 8, nil return i + 8, nil
} }

View File

@ -15,6 +15,7 @@ type User struct {
store Store store Store
messageLog MessageStore messageLog MessageStore
messageIndex MessageSearchProvider messageIndex MessageSearchProvider
lastIP []byte
certificate *tls.Certificate certificate *tls.Certificate
lock sync.Mutex lock sync.Mutex
} }
@ -68,6 +69,21 @@ func (u *User) Remove() {
os.RemoveAll(Path.User(u.Username)) os.RemoveAll(Path.User(u.Username))
} }
func (u *User) GetLastIP() []byte {
u.lock.Lock()
ip := u.lastIP
u.lock.Unlock()
return ip
}
func (u *User) SetLastIP(ip []byte) error {
u.lock.Lock()
u.lastIP = ip
u.lock.Unlock()
return u.store.SaveUser(u)
}
type Server struct { type Server struct {
Name string Name string
Host string Host string