Browse Source

Merge pull request #321 from slingamn/misc.2

eight small changes
tags/v1.0.0-rc1
Daniel Oaks 5 years ago
parent
commit
847922e53d
No account linked to committer's email address
10 changed files with 120 additions and 74 deletions
  1. 13
    7
      irc/channel.go
  2. 1
    7
      irc/client.go
  3. 2
    2
      irc/client_lookup_set.go
  4. 20
    21
      irc/commands.go
  5. 25
    16
      irc/handlers.go
  6. 12
    3
      irc/isupport/list.go
  7. 35
    3
      irc/isupport/list_test.go
  8. 1
    1
      irc/logger/logger.go
  9. 11
    11
      irc/server.go
  10. 0
    3
      oragono.go

+ 13
- 7
irc/channel.go View File

346
 
346
 
347
 // Join joins the given client to this channel (if they can be joined).
347
 // Join joins the given client to this channel (if they can be joined).
348
 func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) {
348
 func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) {
349
+	account := client.Account()
350
+	nickMaskCasefolded := client.NickMaskCasefolded()
351
+
349
 	channel.stateMutex.RLock()
352
 	channel.stateMutex.RLock()
350
 	chname := channel.name
353
 	chname := channel.name
351
 	chcfname := channel.nameCasefolded
354
 	chcfname := channel.nameCasefolded
354
 	limit := channel.userLimit
357
 	limit := channel.userLimit
355
 	chcount := len(channel.members)
358
 	chcount := len(channel.members)
356
 	_, alreadyJoined := channel.members[client]
359
 	_, alreadyJoined := channel.members[client]
360
+	persistentMode := channel.accountToUMode[account]
357
 	channel.stateMutex.RUnlock()
361
 	channel.stateMutex.RUnlock()
358
 
362
 
359
 	if alreadyJoined {
363
 	if alreadyJoined {
361
 		return
365
 		return
362
 	}
366
 	}
363
 
367
 
364
-	account := client.Account()
365
-	nickMaskCasefolded := client.NickMaskCasefolded()
366
-	hasPrivs := isSajoin || (founder != "" && founder == account)
368
+	// the founder can always join (even if they disabled auto +q on join);
369
+	// anyone who automatically receives halfop or higher can always join
370
+	hasPrivs := isSajoin || (founder != "" && founder == account) || (persistentMode != 0 && persistentMode != modes.Voice)
367
 
371
 
368
 	if !hasPrivs && limit != 0 && chcount >= limit {
372
 	if !hasPrivs && limit != 0 && chcount >= limit {
369
 		rb.Add(nil, client.server.name, ERR_CHANNELISFULL, chname, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "l"))
373
 		rb.Add(nil, client.server.name, ERR_CHANNELISFULL, chname, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "l"))
404
 			if newChannel {
408
 			if newChannel {
405
 				givenMode = modes.ChannelOperator
409
 				givenMode = modes.ChannelOperator
406
 			} else {
410
 			} else {
407
-				givenMode = channel.accountToUMode[account]
411
+				givenMode = persistentMode
408
 			}
412
 			}
409
 			if givenMode != 0 {
413
 			if givenMode != 0 {
410
 				channel.members[client].SetMode(givenMode, true)
414
 				channel.members[client].SetMode(givenMode, true)
803
 	nickmask := client.NickMaskString()
807
 	nickmask := client.NickMaskString()
804
 	account := client.AccountName()
808
 	account := client.AccountName()
805
 
809
 
810
+	now := time.Now().UTC()
811
+
806
 	for _, member := range channel.Members() {
812
 	for _, member := range channel.Members() {
807
 		if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
813
 		if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
808
 			// STATUSMSG
814
 			// STATUSMSG
817
 			tagsToUse = clientOnlyTags
823
 			tagsToUse = clientOnlyTags
818
 		}
824
 		}
819
 
825
 
820
-		// TODO(slingamn) evaluate an optimization where we reuse `nickmask` and `account`
821
 		if message == nil {
826
 		if message == nil {
822
-			member.SendFromClient(msgid, client, tagsToUse, cmd, channel.name)
827
+			member.sendFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name)
823
 		} else {
828
 		} else {
824
-			member.SendSplitMsgFromClient(msgid, client, tagsToUse, cmd, channel.name, *message)
829
+			member.sendSplitMsgFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name, *message)
825
 		}
830
 		}
826
 	}
831
 	}
827
 
832
 
831
 		Message:     *message,
836
 		Message:     *message,
832
 		Nick:        nickmask,
837
 		Nick:        nickmask,
833
 		AccountName: account,
838
 		AccountName: account,
839
+		Time:        now,
834
 	})
840
 	})
835
 }
841
 }
836
 
842
 

+ 1
- 7
irc/client.go View File

28
 const (
28
 const (
29
 	// IdentTimeoutSeconds is how many seconds before our ident (username) check times out.
29
 	// IdentTimeoutSeconds is how many seconds before our ident (username) check times out.
30
 	IdentTimeoutSeconds  = 1.5
30
 	IdentTimeoutSeconds  = 1.5
31
-	IRCv3TimestampFormat = "2006-01-02T15:04:05.999Z"
31
+	IRCv3TimestampFormat = "2006-01-02T15:04:05.000Z"
32
 )
32
 )
33
 
33
 
34
 var (
34
 var (
332
 	client.atime = time.Now()
332
 	client.atime = time.Now()
333
 }
333
 }
334
 
334
 
335
-// Touch marks the client as alive (as it it has a connection to us and we
336
-// can receive messages from it).
337
-func (client *Client) Touch() {
338
-	client.idletimer.Touch()
339
-}
340
-
341
 // Ping sends the client a PING message.
335
 // Ping sends the client a PING message.
342
 func (client *Client) Ping() {
336
 func (client *Client) Ping() {
343
 	client.Send(nil, "", "PING", client.nick)
337
 	client.Send(nil, "", "PING", client.nick)

+ 2
- 2
irc/client_lookup_set.go View File

189
 	clients.RLock()
189
 	clients.RLock()
190
 	defer clients.RUnlock()
190
 	defer clients.RUnlock()
191
 	for _, client := range clients.byNick {
191
 	for _, client := range clients.byNick {
192
-		if matcher.Match(client.nickMaskCasefolded) {
192
+		if matcher.Match(client.NickMaskCasefolded()) {
193
 			set.Add(client)
193
 			set.Add(client)
194
 		}
194
 		}
195
 	}
195
 	}
209
 	clients.RLock()
209
 	clients.RLock()
210
 	defer clients.RUnlock()
210
 	defer clients.RUnlock()
211
 	for _, client := range clients.byNick {
211
 	for _, client := range clients.byNick {
212
-		if matcher.Match(client.nickMaskCasefolded) {
212
+		if matcher.Match(client.NickMaskCasefolded()) {
213
 			matchedClient = client
213
 			matchedClient = client
214
 			break
214
 			break
215
 		}
215
 		}

+ 20
- 21
irc/commands.go View File

12
 
12
 
13
 // Command represents a command accepted from a client.
13
 // Command represents a command accepted from a client.
14
 type Command struct {
14
 type Command struct {
15
-	handler           func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
16
-	oper              bool
17
-	usablePreReg      bool
18
-	leaveClientActive bool // if true, leaves the client active time alone. reversed because we can't default a struct element to True
19
-	leaveClientIdle   bool
20
-	minParams         int
21
-	capabs            []string
15
+	handler         func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
16
+	oper            bool
17
+	usablePreReg    bool
18
+	leaveClientIdle bool // if true, leaves the client active time alone
19
+	minParams       int
20
+	capabs          []string
22
 }
21
 }
23
 
22
 
24
 // Run runs this command with the given client/message.
23
 // Run runs this command with the given client/message.
54
 		server.tryRegister(client)
53
 		server.tryRegister(client)
55
 	}
54
 	}
56
 
55
 
57
-	if !cmd.leaveClientIdle {
58
-		client.Touch()
59
-	}
56
+	// most servers do this only for PING/PONG, but we'll do it for any command:
57
+	client.idletimer.Touch()
60
 
58
 
61
-	if !cmd.leaveClientActive {
59
+	if !cmd.leaveClientIdle {
62
 		client.Active()
60
 		client.Active()
63
 	}
61
 	}
64
 
62
 
118
 			minParams: 2,
116
 			minParams: 2,
119
 		},
117
 		},
120
 		"ISON": {
118
 		"ISON": {
121
-			handler:   isonHandler,
122
-			minParams: 1,
119
+			handler:         isonHandler,
120
+			minParams:       1,
121
+			leaveClientIdle: true,
123
 		},
122
 		},
124
 		"JOIN": {
123
 		"JOIN": {
125
 			handler:   joinHandler,
124
 			handler:   joinHandler,
200
 			minParams:    1,
199
 			minParams:    1,
201
 		},
200
 		},
202
 		"PING": {
201
 		"PING": {
203
-			handler:           pingHandler,
204
-			usablePreReg:      true,
205
-			minParams:         1,
206
-			leaveClientActive: true,
202
+			handler:         pingHandler,
203
+			usablePreReg:    true,
204
+			minParams:       1,
205
+			leaveClientIdle: true,
207
 		},
206
 		},
208
 		"PONG": {
207
 		"PONG": {
209
-			handler:           pongHandler,
210
-			usablePreReg:      true,
211
-			minParams:         1,
212
-			leaveClientActive: true,
208
+			handler:         pongHandler,
209
+			usablePreReg:    true,
210
+			minParams:       1,
211
+			leaveClientIdle: true,
213
 		},
212
 		},
214
 		"PRIVMSG": {
213
 		"PRIVMSG": {
215
 			handler:   privmsgHandler,
214
 			handler:   privmsgHandler,

+ 25
- 16
irc/handlers.go View File

2478
 		return false
2478
 		return false
2479
 	}
2479
 	}
2480
 
2480
 
2481
+	handleService := func(nick string) bool {
2482
+		cfnick, _ := CasefoldName(nick)
2483
+		service, ok := OragonoServices[cfnick]
2484
+		if !ok {
2485
+			return false
2486
+		}
2487
+		clientNick := client.Nick()
2488
+		rb.Add(nil, client.server.name, RPL_WHOISUSER, clientNick, service.Name, service.Name, "localhost", "*", fmt.Sprintf(client.t("Network service, for more info /msg %s HELP"), service.Name))
2489
+		// hehe
2490
+		if client.HasMode(modes.TLS) {
2491
+			rb.Add(nil, client.server.name, RPL_WHOISSECURE, clientNick, service.Name, client.t("is using a secure connection"))
2492
+		}
2493
+		return true
2494
+	}
2495
+
2481
 	if client.HasMode(modes.Operator) {
2496
 	if client.HasMode(modes.Operator) {
2482
-		masks := strings.Split(masksString, ",")
2483
-		for _, mask := range masks {
2484
-			casefoldedMask, err := Casefold(mask)
2485
-			if err != nil {
2486
-				rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, client.t("No such nick"))
2487
-				continue
2488
-			}
2489
-			matches := server.clients.FindAll(casefoldedMask)
2490
-			if len(matches) == 0 {
2497
+		for _, mask := range strings.Split(masksString, ",") {
2498
+			matches := server.clients.FindAll(mask)
2499
+			if len(matches) == 0 && !handleService(mask) {
2491
 				rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, client.t("No such nick"))
2500
 				rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, client.t("No such nick"))
2492
 				continue
2501
 				continue
2493
 			}
2502
 			}
2496
 			}
2505
 			}
2497
 		}
2506
 		}
2498
 	} else {
2507
 	} else {
2499
-		// only get the first request
2500
-		casefoldedMask, err := Casefold(strings.Split(masksString, ",")[0])
2501
-		mclient := server.clients.Get(casefoldedMask)
2502
-		if err != nil || mclient == nil {
2503
-			rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, masksString, client.t("No such nick"))
2504
-			// fall through, ENDOFWHOIS is always sent
2505
-		} else {
2508
+		// only get the first request; also require a nick, not a mask
2509
+		nick := strings.Split(masksString, ",")[0]
2510
+		mclient := server.clients.Get(nick)
2511
+		if mclient != nil {
2506
 			client.getWhoisOf(mclient, rb)
2512
 			client.getWhoisOf(mclient, rb)
2513
+		} else if !handleService(nick) {
2514
+			rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, masksString, client.t("No such nick"))
2507
 		}
2515
 		}
2516
+		// fall through, ENDOFWHOIS is always sent
2508
 	}
2517
 	}
2509
 	rb.Add(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, client.t("End of /WHOIS list"))
2518
 	rb.Add(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, client.t("End of /WHOIS list"))
2510
 	return false
2519
 	return false

+ 12
- 3
irc/isupport/list.go View File

3
 
3
 
4
 package isupport
4
 package isupport
5
 
5
 
6
-import "fmt"
7
-import "sort"
6
+import (
7
+	"fmt"
8
+	"sort"
9
+	"strings"
10
+)
8
 
11
 
9
 const (
12
 const (
10
 	maxLastArgLength = 400
13
 	maxLastArgLength = 400
102
 }
105
 }
103
 
106
 
104
 // RegenerateCachedReply regenerates the cached RPL_ISUPPORT reply
107
 // RegenerateCachedReply regenerates the cached RPL_ISUPPORT reply
105
-func (il *List) RegenerateCachedReply() {
108
+func (il *List) RegenerateCachedReply() (err error) {
106
 	il.CachedReply = make([][]string, 0)
109
 	il.CachedReply = make([][]string, 0)
107
 	var length int     // Length of the current cache
110
 	var length int     // Length of the current cache
108
 	var cache []string // Token list cache
111
 	var cache []string // Token list cache
116
 
119
 
117
 	for _, name := range tokens {
120
 	for _, name := range tokens {
118
 		token := getTokenString(name, il.Tokens[name])
121
 		token := getTokenString(name, il.Tokens[name])
122
+		if token[0] == ':' || strings.Contains(token, " ") {
123
+			err = fmt.Errorf("bad isupport token (cannot contain spaces or start with :): %s", token)
124
+			continue
125
+		}
119
 
126
 
120
 		if len(token)+length <= maxLastArgLength {
127
 		if len(token)+length <= maxLastArgLength {
121
 			// account for the space separating tokens
128
 			// account for the space separating tokens
136
 	if len(cache) > 0 {
143
 	if len(cache) > 0 {
137
 		il.CachedReply = append(il.CachedReply, cache)
144
 		il.CachedReply = append(il.CachedReply, cache)
138
 	}
145
 	}
146
+
147
+	return
139
 }
148
 }

+ 35
- 3
irc/isupport/list_test.go View File

26
 	tListLong.AddNoValue("D")
26
 	tListLong.AddNoValue("D")
27
 	tListLong.AddNoValue("E")
27
 	tListLong.AddNoValue("E")
28
 	tListLong.AddNoValue("F")
28
 	tListLong.AddNoValue("F")
29
-	tListLong.RegenerateCachedReply()
29
+	err := tListLong.RegenerateCachedReply()
30
+	if err != nil {
31
+		t.Error(err)
32
+	}
30
 
33
 
31
 	longReplies := [][]string{
34
 	longReplies := [][]string{
32
 		{"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D"},
35
 		{"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D"},
44
 	tList1.Add("INVEX", "i")
47
 	tList1.Add("INVEX", "i")
45
 	tList1.AddNoValue("EXTBAN")
48
 	tList1.AddNoValue("EXTBAN")
46
 	tList1.Add("RANDKILL", "whenever")
49
 	tList1.Add("RANDKILL", "whenever")
47
-	tList1.RegenerateCachedReply()
50
+	err = tList1.RegenerateCachedReply()
51
+	if err != nil {
52
+		t.Error(err)
53
+	}
48
 
54
 
49
 	expected := [][]string{{"CASEMAPPING=rfc1459-strict", "EXTBAN", "INVEX=i", "RANDKILL=whenever", "SASL=yes"}}
55
 	expected := [][]string{{"CASEMAPPING=rfc1459-strict", "EXTBAN", "INVEX=i", "RANDKILL=whenever", "SASL=yes"}}
50
 	if !reflect.DeepEqual(tList1.CachedReply, expected) {
56
 	if !reflect.DeepEqual(tList1.CachedReply, expected) {
58
 	tList2.AddNoValue("INVEX")
64
 	tList2.AddNoValue("INVEX")
59
 	tList2.Add("EXTBAN", "TestBah")
65
 	tList2.Add("EXTBAN", "TestBah")
60
 	tList2.AddNoValue("STABLEKILL")
66
 	tList2.AddNoValue("STABLEKILL")
61
-	tList2.RegenerateCachedReply()
67
+	err = tList2.RegenerateCachedReply()
68
+	if err != nil {
69
+		t.Error(err)
70
+	}
62
 
71
 
63
 	expected = [][]string{{"CASEMAPPING=ascii", "EXTBAN=TestBah", "INVEX", "SASL=yes", "STABLEKILL"}}
72
 	expected = [][]string{{"CASEMAPPING=ascii", "EXTBAN=TestBah", "INVEX", "SASL=yes", "STABLEKILL"}}
64
 	if !reflect.DeepEqual(tList2.CachedReply, expected) {
73
 	if !reflect.DeepEqual(tList2.CachedReply, expected) {
72
 		t.Error("difference reply does not match expected difference reply")
81
 		t.Error("difference reply does not match expected difference reply")
73
 	}
82
 	}
74
 }
83
 }
84
+
85
+func TestBadToken(t *testing.T) {
86
+	list := NewList()
87
+	list.Add("NETWORK", "Bad Network Name")
88
+	list.Add("SASL", "yes")
89
+	list.Add("CASEMAPPING", "rfc1459-strict")
90
+	list.Add("INVEX", "i")
91
+	list.AddNoValue("EXTBAN")
92
+
93
+	err := list.RegenerateCachedReply()
94
+	if err == nil {
95
+		t.Error("isupport token generation should fail due to space in network name")
96
+	}
97
+
98
+	// should produce a list containing the other, valid params
99
+	numParams := 0
100
+	for _, tokenLine := range list.CachedReply {
101
+		numParams += len(tokenLine)
102
+	}
103
+	if numParams != 4 {
104
+		t.Errorf("expected the other 4 params to be generated, got %v", list.CachedReply)
105
+	}
106
+}

+ 1
- 1
irc/logger/logger.go View File

250
 	}
250
 	}
251
 
251
 
252
 	sep := grey(":")
252
 	sep := grey(":")
253
-	fullStringFormatted := fmt.Sprintf("%s %s %s %s %s %s ", timeGrey(time.Now().UTC().Format("2006-01-02T15:04:05Z")), sep, levelDisplay, sep, section(logType), sep)
253
+	fullStringFormatted := fmt.Sprintf("%s %s %s %s %s %s ", timeGrey(time.Now().UTC().Format("2006-01-02T15:04:05.000Z")), sep, levelDisplay, sep, section(logType), sep)
254
 	fullStringRaw := fmt.Sprintf("%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), LogLevelDisplayNames[level], logType)
254
 	fullStringRaw := fmt.Sprintf("%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), LogLevelDisplayNames[level], logType)
255
 	for i, p := range messageParts {
255
 	for i, p := range messageParts {
256
 		fullStringFormatted += p
256
 		fullStringFormatted += p

+ 11
- 11
irc/server.go View File

9
 	"bufio"
9
 	"bufio"
10
 	"crypto/tls"
10
 	"crypto/tls"
11
 	"fmt"
11
 	"fmt"
12
-	"math/rand"
13
 	"net"
12
 	"net"
14
 	"net/http"
13
 	"net/http"
15
 	_ "net/http/pprof"
14
 	_ "net/http/pprof"
148
 }
147
 }
149
 
148
 
150
 // setISupport sets up our RPL_ISUPPORT reply.
149
 // setISupport sets up our RPL_ISUPPORT reply.
151
-func (server *Server) setISupport() {
150
+func (server *Server) setISupport() (err error) {
152
 	maxTargetsString := strconv.Itoa(maxTargets)
151
 	maxTargetsString := strconv.Itoa(maxTargets)
153
 
152
 
154
 	config := server.Config()
153
 	config := server.Config()
193
 		isupport.Add("REGCREDTYPES", "passphrase,certfp")
192
 		isupport.Add("REGCREDTYPES", "passphrase,certfp")
194
 	}
193
 	}
195
 
194
 
196
-	isupport.RegenerateCachedReply()
195
+	err = isupport.RegenerateCachedReply()
196
+	if err != nil {
197
+		return
198
+	}
197
 
199
 
198
 	server.configurableStateMutex.Lock()
200
 	server.configurableStateMutex.Lock()
199
 	server.isupport = isupport
201
 	server.isupport = isupport
200
 	server.configurableStateMutex.Unlock()
202
 	server.configurableStateMutex.Unlock()
203
+	return
201
 }
204
 }
202
 
205
 
203
 func loadChannelList(channel *Channel, list string, maskMode modes.Mode) {
206
 func loadChannelList(channel *Channel, list string, maskMode modes.Mode) {
371
 
374
 
372
 // generateMessageID returns a network-unique message ID.
375
 // generateMessageID returns a network-unique message ID.
373
 func (server *Server) generateMessageID() string {
376
 func (server *Server) generateMessageID() string {
374
-	// we don't need the full like 30 chars since the unixnano below handles
375
-	// most of our uniqueness requirements, so just truncate at 5
376
-	lastbit := strconv.FormatInt(rand.Int63(), 36)
377
-	if 5 < len(lastbit) {
378
-		lastbit = lastbit[:4]
379
-	}
380
-	return fmt.Sprintf("%s%s", strconv.FormatInt(time.Now().UTC().UnixNano(), 36), lastbit)
377
+	return utils.GenerateSecretToken()
381
 }
378
 }
382
 
379
 
383
 //
380
 //
794
 	// set RPL_ISUPPORT
791
 	// set RPL_ISUPPORT
795
 	var newISupportReplies [][]string
792
 	var newISupportReplies [][]string
796
 	oldISupportList := server.ISupport()
793
 	oldISupportList := server.ISupport()
797
-	server.setISupport()
794
+	err = server.setISupport()
795
+	if err != nil {
796
+		return err
797
+	}
798
 	if oldISupportList != nil {
798
 	if oldISupportList != nil {
799
 		newISupportReplies = oldISupportList.GetDifference(server.ISupport())
799
 		newISupportReplies = oldISupportList.GetDifference(server.ISupport())
800
 	}
800
 	}

+ 0
- 3
oragono.go View File

8
 import (
8
 import (
9
 	"fmt"
9
 	"fmt"
10
 	"log"
10
 	"log"
11
-	"math/rand"
12
 	"strings"
11
 	"strings"
13
 	"syscall"
12
 	"syscall"
14
-	"time"
15
 
13
 
16
 	"github.com/docopt/docopt-go"
14
 	"github.com/docopt/docopt-go"
17
 	"github.com/oragono/oragono/irc"
15
 	"github.com/oragono/oragono/irc"
114
 			}
112
 			}
115
 		}
113
 		}
116
 	} else if arguments["run"].(bool) {
114
 	} else if arguments["run"].(bool) {
117
-		rand.Seed(time.Now().UTC().UnixNano())
118
 		if !arguments["--quiet"].(bool) {
115
 		if !arguments["--quiet"].(bool) {
119
 			logman.Info("startup", fmt.Sprintf("Oragono v%s starting", irc.SemVer))
116
 			logman.Info("startup", fmt.Sprintf("Oragono v%s starting", irc.SemVer))
120
 			if commit == "" {
117
 			if commit == "" {

Loading…
Cancel
Save