mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-10-31 13:57:23 +01:00 
			
		
		
		
	Merge pull request #1458 from slingamn/issue1260_channel_forward.1
fix #1260
This commit is contained in:
		
						commit
						0d1521d4c4
					
				| @ -94,6 +94,13 @@ def convert(infile): | ||||
|                 out['channels'][chname]['topicSetBy'] = parts[3] | ||||
|             elif category == 'private:topic:ts': | ||||
|                 out['channels'][chname]['topicSetAt'] = to_unixnano(parts[3]) | ||||
|             elif category == 'private:mlockext': | ||||
|                 # the channel forward mode is +L on insp/unreal, +f on charybdis | ||||
|                 # charybdis has a +L ("large banlist") taking no argument | ||||
|                 # and unreal has a +f ("flood limit") taking two colon-delimited numbers, | ||||
|                 # so check for an argument that starts with a # | ||||
|                 if parts[3].startswith('L#') or parts[3].startswith('f#'): | ||||
|                     out['channels'][chname]['forward'] = parts[3][1:] | ||||
|         elif category == 'CA': | ||||
|             # channel access lists | ||||
|             # CA #mychannel shivaram +AFORafhioqrstv 1600134478 shivaram | ||||
|  | ||||
| @ -28,6 +28,7 @@ type Channel struct { | ||||
| 	flags             modes.ModeSet | ||||
| 	lists             map[modes.Mode]*UserMaskSet | ||||
| 	key               string | ||||
| 	forward           string | ||||
| 	members           MemberSet | ||||
| 	membersCache      []*Client // allow iteration over channel members without holding the lock | ||||
| 	name              string | ||||
| @ -133,6 +134,7 @@ func (channel *Channel) applyRegInfo(chanReg RegisteredChannel) { | ||||
| 	channel.key = chanReg.Key | ||||
| 	channel.userLimit = chanReg.UserLimit | ||||
| 	channel.settings = chanReg.Settings | ||||
| 	channel.forward = chanReg.Forward | ||||
| 
 | ||||
| 	for _, mode := range chanReg.Modes { | ||||
| 		channel.flags.SetMode(mode, true) | ||||
| @ -163,6 +165,7 @@ func (channel *Channel) ExportRegistration(includeFlags uint) (info RegisteredCh | ||||
| 
 | ||||
| 	if includeFlags&IncludeModes != 0 { | ||||
| 		info.Key = channel.key | ||||
| 		info.Forward = channel.forward | ||||
| 		info.Modes = channel.flags.AllModes() | ||||
| 		info.UserLimit = channel.userLimit | ||||
| 	} | ||||
| @ -612,20 +615,27 @@ func (channel *Channel) modeStrings(client *Client) (result []string) { | ||||
| 	isMember := hasPrivs || channel.members[client] != nil | ||||
| 	showKey := isMember && (channel.key != "") | ||||
| 	showUserLimit := channel.userLimit > 0 | ||||
| 	showForward := channel.forward != "" | ||||
| 
 | ||||
| 	mods := "+" | ||||
| 	var mods strings.Builder | ||||
| 	mods.WriteRune('+') | ||||
| 
 | ||||
| 	// flags with args | ||||
| 	if showKey { | ||||
| 		mods += modes.Key.String() | ||||
| 		mods.WriteRune(rune(modes.Key)) | ||||
| 	} | ||||
| 	if showUserLimit { | ||||
| 		mods += modes.UserLimit.String() | ||||
| 		mods.WriteRune(rune(modes.UserLimit)) | ||||
| 	} | ||||
| 	if showForward { | ||||
| 		mods.WriteRune(rune(modes.Forward)) | ||||
| 	} | ||||
| 
 | ||||
| 	mods += channel.flags.String() | ||||
| 	for _, m := range channel.flags.AllModes() { | ||||
| 		mods.WriteRune(rune(m)) | ||||
| 	} | ||||
| 
 | ||||
| 	result = []string{mods} | ||||
| 	result = []string{mods.String()} | ||||
| 
 | ||||
| 	// args for flags with args: The order must match above to keep | ||||
| 	// positional arguments in place. | ||||
| @ -635,6 +645,9 @@ func (channel *Channel) modeStrings(client *Client) (result []string) { | ||||
| 	if showUserLimit { | ||||
| 		result = append(result, strconv.Itoa(channel.userLimit)) | ||||
| 	} | ||||
| 	if showForward { | ||||
| 		result = append(result, channel.forward) | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| @ -694,7 +707,7 @@ func (channel *Channel) AddHistoryItem(item history.Item, account string) (err e | ||||
| } | ||||
| 
 | ||||
| // Join joins the given client to this channel (if they can be joined). | ||||
| func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) error { | ||||
| func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) (joinErr error, forward string) { | ||||
| 	details := client.Details() | ||||
| 
 | ||||
| 	channel.stateMutex.RLock() | ||||
| @ -707,11 +720,12 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp | ||||
| 	chcount := len(channel.members) | ||||
| 	_, alreadyJoined := channel.members[client] | ||||
| 	persistentMode := channel.accountToUMode[details.account] | ||||
| 	forward = channel.forward | ||||
| 	channel.stateMutex.RUnlock() | ||||
| 
 | ||||
| 	if alreadyJoined { | ||||
| 		// no message needs to be sent | ||||
| 		return nil | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	// 0. SAJOIN always succeeds | ||||
| @ -723,32 +737,33 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp | ||||
| 		client.CheckInvited(chcfname, createdAt) | ||||
| 	if !hasPrivs { | ||||
| 		if limit != 0 && chcount >= limit { | ||||
| 			return errLimitExceeded | ||||
| 			return errLimitExceeded, forward | ||||
| 		} | ||||
| 
 | ||||
| 		if chkey != "" && !utils.SecretTokensMatch(chkey, key) { | ||||
| 			return errWrongChannelKey | ||||
| 			return errWrongChannelKey, forward | ||||
| 		} | ||||
| 
 | ||||
| 		if channel.flags.HasMode(modes.InviteOnly) && | ||||
| 			!channel.lists[modes.InviteMask].Match(details.nickMaskCasefolded) { | ||||
| 			return errInviteOnly | ||||
| 			return errInviteOnly, forward | ||||
| 		} | ||||
| 
 | ||||
| 		if channel.lists[modes.BanMask].Match(details.nickMaskCasefolded) && | ||||
| 			!channel.lists[modes.ExceptMask].Match(details.nickMaskCasefolded) && | ||||
| 			!channel.lists[modes.InviteMask].Match(details.nickMaskCasefolded) { | ||||
| 			return errBanned | ||||
| 			// do not forward people who are banned: | ||||
| 			return errBanned, "" | ||||
| 		} | ||||
| 
 | ||||
| 		if details.account == "" && | ||||
| 			(channel.flags.HasMode(modes.RegisteredOnly) || channel.server.Defcon() <= 2) { | ||||
| 			return errRegisteredOnly | ||||
| 			return errRegisteredOnly, forward | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if joinErr := client.addChannel(channel, rb == nil); joinErr != nil { | ||||
| 		return joinErr | ||||
| 		return joinErr, "" | ||||
| 	} | ||||
| 
 | ||||
| 	client.server.logger.Debug("join", fmt.Sprintf("%s joined channel %s", details.nick, chname)) | ||||
| @ -795,7 +810,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp | ||||
| 	} | ||||
| 
 | ||||
| 	if rb == nil { | ||||
| 		return nil | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	var modestr string | ||||
| @ -858,7 +873,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp | ||||
| 	rb.Flush(true) | ||||
| 
 | ||||
| 	channel.autoReplayHistory(client, rb, message.Msgid) | ||||
| 	return nil | ||||
| 	return nil, "" | ||||
| } | ||||
| 
 | ||||
| func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, skipMsgid string) { | ||||
|  | ||||
| @ -83,12 +83,12 @@ func (cm *ChannelManager) Get(name string) (channel *Channel) { | ||||
| } | ||||
| 
 | ||||
| // Join causes `client` to join the channel named `name`, creating it if necessary. | ||||
| func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error { | ||||
| func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) (err error, forward string) { | ||||
| 	server := client.server | ||||
| 	casefoldedName, err := CasefoldChannel(name) | ||||
| 	skeleton, skerr := Skeleton(name) | ||||
| 	if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen { | ||||
| 		return errNoSuchChannel | ||||
| 		return errNoSuchChannel, "" | ||||
| 	} | ||||
| 
 | ||||
| 	channel, err := func() (*Channel, error) { | ||||
| @ -128,15 +128,15 @@ func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin | ||||
| 	}() | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return err, "" | ||||
| 	} | ||||
| 
 | ||||
| 	channel.EnsureLoaded() | ||||
| 	err = channel.Join(client, key, isSajoin, rb) | ||||
| 	err, forward = channel.Join(client, key, isSajoin, rb) | ||||
| 
 | ||||
| 	cm.maybeCleanup(channel, true) | ||||
| 
 | ||||
| 	return err | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) { | ||||
|  | ||||
| @ -35,6 +35,7 @@ const ( | ||||
| 	keyChannelAccountToUMode = "channel.accounttoumode %s" | ||||
| 	keyChannelUserLimit      = "channel.userlimit %s" | ||||
| 	keyChannelSettings       = "channel.settings %s" | ||||
| 	keyChannelForward        = "channel.forward %s" | ||||
| 
 | ||||
| 	keyChannelPurged = "channel.purged %s" | ||||
| ) | ||||
| @ -56,6 +57,7 @@ var ( | ||||
| 		keyChannelAccountToUMode, | ||||
| 		keyChannelUserLimit, | ||||
| 		keyChannelSettings, | ||||
| 		keyChannelForward, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| @ -94,6 +96,8 @@ type RegisteredChannel struct { | ||||
| 	Modes []modes.Mode | ||||
| 	// Key represents the channel key / password | ||||
| 	Key string | ||||
| 	// Forward is the forwarding/overflow (+f) channel | ||||
| 	Forward string | ||||
| 	// UserLimit is the user limit (0 for no limit) | ||||
| 	UserLimit int | ||||
| 	// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h) | ||||
| @ -208,6 +212,7 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC | ||||
| 		password, _ := tx.Get(fmt.Sprintf(keyChannelPassword, channelKey)) | ||||
| 		modeString, _ := tx.Get(fmt.Sprintf(keyChannelModes, channelKey)) | ||||
| 		userLimitString, _ := tx.Get(fmt.Sprintf(keyChannelUserLimit, channelKey)) | ||||
| 		forward, _ := tx.Get(fmt.Sprintf(keyChannelForward, channelKey)) | ||||
| 		banlistString, _ := tx.Get(fmt.Sprintf(keyChannelBanlist, channelKey)) | ||||
| 		exceptlistString, _ := tx.Get(fmt.Sprintf(keyChannelExceptlist, channelKey)) | ||||
| 		invitelistString, _ := tx.Get(fmt.Sprintf(keyChannelInvitelist, channelKey)) | ||||
| @ -249,6 +254,7 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC | ||||
| 			AccountToUMode: accountToUMode, | ||||
| 			UserLimit:      int(userLimit), | ||||
| 			Settings:       settings, | ||||
| 			Forward:        forward, | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| @ -361,6 +367,7 @@ func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredCha | ||||
| 		modeString := modes.Modes(channelInfo.Modes).String() | ||||
| 		tx.Set(fmt.Sprintf(keyChannelModes, channelKey), modeString, nil) | ||||
| 		tx.Set(fmt.Sprintf(keyChannelUserLimit, channelKey), strconv.Itoa(channelInfo.UserLimit), nil) | ||||
| 		tx.Set(fmt.Sprintf(keyChannelForward, channelKey), channelInfo.Forward, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	if includeFlags&IncludeLists != 0 { | ||||
|  | ||||
| @ -517,3 +517,9 @@ func (channel *Channel) SetSettings(settings ChannelSettings) { | ||||
| 	channel.stateMutex.Unlock() | ||||
| 	channel.MarkDirty(IncludeSettings) | ||||
| } | ||||
| 
 | ||||
| func (channel *Channel) setForward(forward string) { | ||||
| 	channel.stateMutex.Lock() | ||||
| 	channel.forward = forward | ||||
| 	channel.stateMutex.Unlock() | ||||
| } | ||||
|  | ||||
| @ -1192,9 +1192,16 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp | ||||
| 		if len(keys) > i { | ||||
| 			key = keys[i] | ||||
| 		} | ||||
| 		err := server.channels.Join(client, name, key, false, rb) | ||||
| 		err, forward := server.channels.Join(client, name, key, false, rb) | ||||
| 		if err != nil { | ||||
| 			sendJoinError(client, name, rb, err) | ||||
| 			if forward != "" { | ||||
| 				rb.Add(nil, server.name, ERR_LINKCHANNEL, client.Nick(), utils.SafeErrorParam(name), forward, client.t("Forwarding to another channel")) | ||||
| 				name = forward | ||||
| 				err, _ = server.channels.Join(client, name, key, false, rb) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				sendJoinError(client, name, rb, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| @ -1255,7 +1262,7 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re | ||||
| 
 | ||||
| 	channels := strings.Split(channelString, ",") | ||||
| 	for _, chname := range channels { | ||||
| 		err := server.channels.Join(target, chname, "", true, rb) | ||||
| 		err, _ := server.channels.Join(target, chname, "", true, rb) | ||||
| 		if err != nil { | ||||
| 			sendJoinError(client, chname, rb, err) | ||||
| 		} | ||||
|  | ||||
| @ -44,6 +44,7 @@ type channelImport struct { | ||||
| 	Modes        string | ||||
| 	Key          string | ||||
| 	Limit        int | ||||
| 	Forward      string | ||||
| } | ||||
| 
 | ||||
| type databaseImport struct { | ||||
| @ -187,6 +188,11 @@ func doImportDBGeneric(config *Config, dbImport databaseImport, credsType Creden | ||||
| 		if chInfo.Limit > 0 { | ||||
| 			tx.Set(fmt.Sprintf(keyChannelUserLimit, cfchname), strconv.Itoa(chInfo.Limit), nil) | ||||
| 		} | ||||
| 		if chInfo.Forward != "" { | ||||
| 			if _, err := CasefoldChannel(chInfo.Forward); err == nil { | ||||
| 				tx.Set(fmt.Sprintf(keyChannelForward, cfchname), chInfo.Forward, nil) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if warnSkeletons { | ||||
|  | ||||
							
								
								
									
										25
									
								
								irc/modes.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								irc/modes.go
									
									
									
									
									
								
							| @ -12,6 +12,7 @@ import ( | ||||
| 
 | ||||
| 	"github.com/oragono/oragono/irc/modes" | ||||
| 	"github.com/oragono/oragono/irc/sno" | ||||
| 	"github.com/oragono/oragono/irc/utils" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -254,6 +255,28 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c | ||||
| 				applied = append(applied, change) | ||||
| 			} | ||||
| 
 | ||||
| 		case modes.Forward: | ||||
| 			switch change.Op { | ||||
| 			case modes.Add: | ||||
| 				ch := client.server.channels.Get(change.Arg) | ||||
| 				if ch == nil { | ||||
| 					rb.Add(nil, client.server.name, ERR_INVALIDMODEPARAM, details.nick, utils.SafeErrorParam(change.Arg), fmt.Sprintf(client.t("No such channel"))) | ||||
| 				} else if ch == channel { | ||||
| 					rb.Add(nil, client.server.name, ERR_INVALIDMODEPARAM, details.nick, utils.SafeErrorParam(change.Arg), fmt.Sprintf(client.t("You can't forward a channel to itself"))) | ||||
| 				} else { | ||||
| 					if !ch.ClientIsAtLeast(client, modes.ChannelOperator) { | ||||
| 						rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, details.nick, ch.Name(), client.t("You must be a channel operator in the channel you are forwarding to")) | ||||
| 					} else { | ||||
| 						change.Arg = ch.Name() | ||||
| 						channel.setForward(change.Arg) | ||||
| 						applied = append(applied, change) | ||||
| 					} | ||||
| 				} | ||||
| 			case modes.Remove: | ||||
| 				channel.setForward("") | ||||
| 				applied = append(applied, change) | ||||
| 			} | ||||
| 
 | ||||
| 		case modes.Key: | ||||
| 			switch change.Op { | ||||
| 			case modes.Add: | ||||
| @ -302,7 +325,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c | ||||
| 		case modes.BanMask, modes.ExceptMask, modes.InviteMask: | ||||
| 			includeFlags |= IncludeLists | ||||
| 		case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice: | ||||
| 			// these are never persisted currently, but might be in the future (see discussion on #729) | ||||
| 			// these are persisted on the client object, via (*Channel).applyModeToMember | ||||
| 		default: | ||||
| 			includeFlags |= IncludeModes | ||||
| 		} | ||||
|  | ||||
| @ -24,7 +24,7 @@ var ( | ||||
| 	SupportedChannelModes = Modes{ | ||||
| 		BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key, | ||||
| 		Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, RegisteredOnlySpeak, | ||||
| 		Secret, UserLimit, NoCTCP, Auditorium, OpModerated, | ||||
| 		Secret, UserLimit, NoCTCP, Auditorium, OpModerated, Forward, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| @ -130,6 +130,7 @@ const ( | ||||
| 	UserLimit           Mode = 'l' // flag arg | ||||
| 	NoCTCP              Mode = 'C' // flag | ||||
| 	OpModerated         Mode = 'U' // flag | ||||
| 	Forward             Mode = 'f' // flag arg | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -277,7 +278,7 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) { | ||||
| 				} else { | ||||
| 					continue | ||||
| 				} | ||||
| 			case UserLimit: | ||||
| 			case UserLimit, Forward: | ||||
| 				// don't require value when removing | ||||
| 				if change.Op == Add { | ||||
| 					if len(params) > skipArgs { | ||||
| @ -445,7 +446,7 @@ func RplMyInfo() (param1, param2, param3 string) { | ||||
| 	sort.Sort(ByCodepoint(channelModes)) | ||||
| 
 | ||||
| 	// XXX enumerate these by hand, i can't see any way to DRY this | ||||
| 	channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit} | ||||
| 	channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit, Forward} | ||||
| 	channelParametrizedModes = append(channelParametrizedModes, ChannelUserModes...) | ||||
| 	sort.Sort(ByCodepoint(channelParametrizedModes)) | ||||
| 
 | ||||
| @ -459,7 +460,7 @@ func ChanmodesToken() (result string) { | ||||
| 	// type B: modes with parameters | ||||
| 	B := Modes{Key} | ||||
| 	// type C: modes that take a parameter only when set, never when unset | ||||
| 	C := Modes{UserLimit} | ||||
| 	C := Modes{UserLimit, Forward} | ||||
| 	// type D: modes without parameters | ||||
| 	D := Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret, NoCTCP, RegisteredOnly, RegisteredOnlySpeak, Auditorium, OpModerated} | ||||
| 
 | ||||
|  | ||||
| @ -149,6 +149,7 @@ const ( | ||||
| 	ERR_YOUWILLBEBANNED           = "466" | ||||
| 	ERR_KEYSET                    = "467" | ||||
| 	ERR_INVALIDUSERNAME           = "468" | ||||
| 	ERR_LINKCHANNEL               = "470" | ||||
| 	ERR_CHANNELISFULL             = "471" | ||||
| 	ERR_UNKNOWNMODE               = "472" | ||||
| 	ERR_INVITEONLYCHAN            = "473" | ||||
|  | ||||
							
								
								
									
										2
									
								
								irctest
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								irctest
									
									
									
									
									
								
							| @ -1 +1 @@ | ||||
| Subproject commit 5aeb297de549c714ca692eda87d72b179d039ca6 | ||||
| Subproject commit 40ac45cdbe8ba3165487b2af6fb9d8684ec3a297 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Shivaram Lingamneni
						Shivaram Lingamneni