How to connect N number of values to RGB combine node and to have all possible variations
Here's the script I came up with. I called the panel "Socket Arranger", and I put it in a new tab called "Socket" in the node editor. 

Here's the main logic behind creating the links: 


Focus on two groups: 1. the output sockets of one node and 2. the
input sockets of another node.
I created sets from the output sockets. Each set should be unique (it
shouldn't share sockets with other sets). That way, I can consider
each set separately when thinking about how they should be connected
to the input sockets. Therefore, use itertools.combinations.
The size of all sets made from itertools.combinations should be the
same as the amount of input sockets. For example, only 3 output
sockets can connect to 3 input sockets. (I didn't consider
disconnected sockets. I don't know if you need it. Plus it sounds
hard to do haha.)
For each unique set, I'm looking for as many input setups as
possible, so it doesn't matter what order I connect them to the input
sockets. When I dealt with the input sockets, I used
itertools.permutations.
Each unique set made from itertools.combinations contains the same amount of input socket possiblilities (since we're only doing itertools.permutations on the same set of input sockets). So to find the total amount of output/input
combinations, I multiplied len(itertools.combinations) of output sockets with len(itertools.permutations) of input sockets. Basically, in this case, "21 C 3" * "3 P 3" = 7980 unique combinations.
@PGMath was right.
Overall, the code was about mixing a combination of output sockets with a permutation of input sockets.


Code:

import bpy, itertools
from bpy.types import Panel, PropertyGroup, Operator
from bpy.props import StringProperty, IntProperty, PointerProperty, CollectionProperty, BoolProperty
#I make my own term called a 'node map'. I call it a list of tuples with information about how the sockets should be linked. A larger CollectionProperty() stores a list of node maps.'

def updateBeforeFrame(scene):
    '''force update of node setups before the frame is rendered'''
    #This looks repetitive, but apparently this explicit assignment calls the proper "update" functions. Otherwise, it doesn't work, even if you see the index inputs in the Socket Arranger Panel changing
    if scene.inputGroup.combOrIso == True:
        scene.inputGroup.combIndex = scene.inputGroup.combIndex
    else:
       scene.inputGroup.isolateIndex = scene.inputGroup.isolateIndex

bpy.app.handlers.frame_change_pre.append(updateBeforeFrame)

def linkNodes(self,context):
    '''get all the node maps generated from the "Set Up Combinations" button, and interface with "Combination Index" slider to access the maps''' 
    mt = bpy.data.materials[context.scene.inputGroup.mat]
    nOu = mt.node_tree.nodes[context.scene.inputGroup.nodeOut]
    nIn = mt.node_tree.nodes[context.scene.inputGroup.nodeIn]

    intIndex = context.scene.inputGroup.combIndex

    set = context.scene.combSet[intIndex]
    lst = eval(set.miSetString) #convert list earlier converted to a string back to a list
    for output, input in lst:
        mt.node_tree.links.new(nIn.inputs[input], nOu.outputs[output])
    mt.node_tree.update_tag()


def linkIsolatedNodes(self,context):
    '''same as linkNodes, but with the "Isolate String" button and the "Isolate Index" button'''  
    mt = bpy.data.materials[context.scene.inputGroup.mat]
    nOu = mt.node_tree.nodes[context.scene.inputGroup.nodeOut]
    nIn = mt.node_tree.nodes[context.scene.inputGroup.nodeIn]

    intIndex = context.scene.inputGroup.isolateIndex

    set = context.scene.isoCombSet[intIndex]
    lst = eval(set.miSetString)
    for output, input in lst:
        mt.node_tree.links.new(nIn.inputs[input], nOu.outputs[output])        
    mt.node_tree.update_tag()

class CombinationSet(PropertyGroup):
    '''a holder for each list of node maps generated from "the "Set Up Combinations" button''' 

    miSetString = StringProperty()

class IsolatedCombinationSet(PropertyGroup):
    '''same as CombinationSet, but for "Isolate String button'''

    miSetString = StringProperty()

class InputGroup(PropertyGroup):
    '''hold inputs for the script's panel here'''
    #I can set soft_min to 0 for all IntProperties here since when I index any CollectionProperty, it always starts at 0

    combIndex = IntProperty(name="Combination Index", soft_min = 0, update=linkNodes)

    mat = StringProperty(name="Material Name")

    nodeOut = StringProperty(name="Node for Outputs Name",
    description="The name of the node to get output sockets from. (in the Node Panel)")

    nodeIn = StringProperty(name="Node for Inputs Name",
    description="The name of the node to get input sockets from. (in the Node Panel)")

    inOrExclude = BoolProperty(name="Must Contain All Listed?", 
    description = "If yes, isolate only the combinations containing all of the sockets. If no, look for items with at least one of the sockets listed. (The original combination list won't be affected.)",
    default = False)

    isolateString = StringProperty(name="Isolate String", 
    description="Type in the names of either input or output socket node names, separated by commas, to choose specific combinations. (Leave empty to get the original combination list)")

    isolateIndex = IntProperty(name="Isolate Index", soft_min = 0, update=linkIsolatedNodes)

    combOrIso = BoolProperty(name="Render combination keyframes? (else isolated keyframes)",
    description="If yes, the scene will render out keyframes put on the 'Combination Index' slider. If no, it'll render out the 'Isolate Index' slider.")


class SetUpCombinations(Operator):
    '''set up a list of "node maps"'''    
    bl_idname="custom.set_up_combinations"
    bl_label = "Set Up Combinations"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        mt = bpy.data.materials[context.scene.inputGroup.mat]
        nOu = mt.node_tree.nodes[context.scene.inputGroup.nodeOut]
        nIn = mt.node_tree.nodes[context.scene.inputGroup.nodeIn]

        nOuNames = [] 
        nInNames = []
        for socket in nOu.outputs.values():
            nOuNames.append(socket.name)
        for socket in nIn.inputs.values():
            nInNames.append(socket.name)
        miniSetSize = len(nIn.inputs.values())

        comb = list(itertools.combinations(nOuNames, miniSetSize))
        permu = list(itertools.permutations(nInNames, miniSetSize))
        cSet = context.scene.combSet
        cSet.clear()

        for com in comb:
            for per in permu:
                newSet = cSet.add()
                miniSet = list(zip(com, per))
                newSet.miSetString = str(miniSet) #convert to string so can be stored in a StringProperty for long-term usage

        context.scene.inputGroup.maxIndexLabel = str(len(cSet) - 1)
        return {"FINISHED"}

class IsolateCombinations(Operator):
    '''same as SetUpCombinations, but with the option to isolate certain node maps by socket name'''
    bl_idname = "custom.isolate_combinations"
    bl_label = "Isolate Combinations"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        iCoSet = context.scene.isoCombSet
        iCoSet.clear()

        stringLst = context.scene.inputGroup.isolateString.strip('[]').split(',')
        for i in stringLst:
            stringLst[stringLst.index(i)] = i.strip()

        if context.scene.inputGroup.inOrExclude == True:
            for set in context.scene.combSet:
                add = True
                for string in stringLst:
                    if string not in set.miSetString:
                        add = False
                        pass
                if add:
                    newSet = iCoSet.add()
                    newSet.miSetString = set.miSetString            
        else:
            for set in context.scene.combSet:
                add = False
                for string in stringLst:
                    if string in set.miSetString:
                        add = True
                        pass
                if add:
                    newSet = iCoSet.add()
                    newSet.miSetString = set.miSetString
        return {"FINISHED"}        




class SocketArranger(Panel):
    bl_idname = "socket_arranger"
    bl_label = "Socket Arranger"
    bl_space_type = "NODE_EDITOR"   
    bl_region_type = "TOOLS"    
    bl_category = "Sockets"
    bl_context = "objectmode"   


    def draw(self, context):
        lt = self.layout
        iGroup = context.scene.inputGroup

        length = len(context.scene.combSet)
        maxIndex = length - 1
        if length == 0:
            maxIndex = 0

        lt.prop(iGroup, "combIndex")
        lt.label(text="Max Comination Index: " + str(maxIndex))
        lt.label(text="(Max Combinations: " + str(length) + ")")
        lt.prop(iGroup, "mat")
        lt.prop(iGroup, "nodeOut")
        lt.prop(iGroup, "nodeIn")
        lt.operator("custom.set_up_combinations")
        lt.prop(iGroup, "isolateString")
        lt.operator("custom.isolate_combinations")
        lt.prop(iGroup, "isolateIndex")
        lt.prop(iGroup, "inOrExclude")

        length = len(context.scene.isoCombSet)
        maxIndex = length - 1
        if length == 0:
            maxIndex = 0        
        lt.label(text="Max Isolate Index: " + str(maxIndex))
        lt.label(text="(Number of Isolated Combinations: " + str(length) + ")")

        lt.prop(iGroup, "combOrIso")


def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.inputGroup = PointerProperty(type=InputGroup)
    bpy.types.Scene.combSet = CollectionProperty(type=CombinationSet) 
    bpy.types.Scene.isoCombSet = CollectionProperty(type=IsolatedCombinationSet)

def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.inputGroup
    del bpy.types.Scene.combSet
    del bpy.types.Scene.isoCombSet

if __name__ == "__main__":
    register()




I also added some options to allow you to isolate certain socket set ups to experiment with. I thought it might help you, especially with 21 output sockets. This doesn't work for nodes while you're editing a node group. If I can get it done, I'll try to add that in, maybe along with the "disconnected sockets" scenarios.