[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[Fedora-directory-devel] Please review: distributed numeric assignment plugin



New pre-operation plugin:

General numeric sequencer that allows generation of sequenced unique numbers such as posix uidNumber, posix gidNumber, Samba SambaSID etc. in an MMR environment. Rather than network locking or number pooling schemes, the approach taken is to allow the configuration of an interval that should be equal to or greater than the number of masters in the deployment. Then each master is assigned starting numbers in sequence e.g. server 1, 500; server 2, 501; server 3, 502 etc. When the interval is configured as 3, the servers will generate sequences like so: server 1, 500, 503, 506...; server 2, 501, 504, 507; server 3, 502, 505, 508. Of course, in single master environments the interval may be set to one for a monotonically increasing sequence. Adding masters that exceed the interval requires that the configuration be reset, starting from a value higher than the highest currently assigned value.

Multiple types may be configured. An LDAP search filter must be added to the configuration for each type, the filter may be as complex as desired and determines to which entries the configuration applies - at minimum this filter should restrict the configuration to objectclasses that allow the type since no schema checking is done by the plugin. Generated values may have a pre-fix configured to be pre-pended to the value (useful for systems such as Samba when it needs a new SID). Each configuration entry may be scoped with a configuration attribute containing a DN, where conflicts arise due to scope the closest scope wins.

Configuration is dynamic. That is, once the plugin entry has been added to cn=plugins, cn=config and the server restarted, any configuration change will be observed. Configuration entries that do not contain an attribute type, a starting value, an interval value, and a filter will be ignored. Which configuration entries have been skipped can be determined by config level logging.

Example configuration ldif and test ldaifs along with some scripts are included. The main source is in dna.c.

--
Pete

#
# BEGIN COPYRIGHT BLOCK
# This Program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 2 of the License.
# 
# This Program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA.
# 
# In addition, as a special exception, Red Hat, Inc. gives You the additional
# right to link the code of this Program with code not covered under the GNU
# General Public License ("Non-GPL Code") and to distribute linked combinations
# including the two, subject to the limitations in this paragraph. Non-GPL Code
# permitted under this exception must only link to the code of this Program
# through those well defined interfaces identified in the file named EXCEPTION
# found in the source code files (the "Approved Interfaces"). The files of
# Non-GPL Code may instantiate templates or use macros or inline functions from
# the Approved Interfaces without causing the resulting work to be covered by
# the GNU General Public License. Only Red Hat, Inc. may make changes or
# additions to the list of Approved Interfaces. You must obey the GNU General
# Public License in all respects for all of the Program code and other code used
# in conjunction with the Program except the Non-GPL Code covered by this
# exception. If you modify this file, you may extend this exception to your
# version of the file, but you are not obligated to do so. If you do not wish to
# provide this exception without modification, you must delete this exception
# statement from your version and license this file solely under the GPL without
# exception. 
# 
# 
# Copyright (C) 2007 Red Hat, Inc.
# All rights reserved.
# END COPYRIGHT BLOCK
#
LDAP_SRC = ../../..
BUILD_ROOT = ../../../..

NOSTDCLEAN=true # don't let nsconfig.mk define target clean
NOSTDSTRIP=true # don't let nsconfig.mk define target strip

OBJDEST = $(OBJDIR)/lib/libdna
LIBDIR = $(LDAP_PLUGIN_RELDIR)

include $(BUILD_ROOT)/nsdefs.mk
include $(BUILD_ROOT)/nsconfig.mk
include $(LDAP_SRC)/nsldap.mk

ifeq ($(ARCH), WINNT)
DEF_FILE:=./dna.def
endif

DNA_OBJS =	dna.o
OBJS = $(addprefix $(OBJDEST)/, $(DNA_OBJS)) 

DNA_DLL = libdna-plugin

INCLUDES += -I../http -I../../slapd  -I../../../include  
CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING

ifeq ($(ARCH), WINNT)
EXTRA_LIBS_DEP +=   $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
EXTRA_LIBS_DEP +=   $(LDAP_COMMON_LIBS_DEP)
EXTRA_LIBS += $(NSPRLINK)  $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
EXTRA_LIBS += $(LDAP_COMMON_LIBS)
DNA_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
endif

ifeq ($(ARCH), AIX)
EXTRA_LIBS_DEP +=   $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
EXTRA_LIBS_DEP +=   $(LDAP_COMMON_LIBS_DEP)
EXTRA_LIBS += $(LIBSLAPDLINK)  $(NSPRLINK)  $(LDAP_SDK_LIBLDAP_DLL)
EXTRA_LIBS += $(LDAP_COMMON_LIBS)
LD=ld
endif

ifeq ($(ARCH), HPUX)
EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
EXTRA_LIBS_DEP +=   $(LDAP_COMMON_LIBS_DEP)
EXTRA_LIBS += $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
EXTRA_LIBS += $(LDAP_COMMON_LIBS)
endif

DNA=	$(addprefix $(LIBDIR)/, $(DNA_DLL).$(DLL_SUFFIX))

clientSDK: 

all:	$(OBJDEST) $(LIBDIR) $(DNA)

ifeq ($(ARCH), WINNT)
$(DNA): $(OBJS) $(DNA_DLL_OBJ) $(DEF_FILE)
	$(LINK_DLL) $(DNA_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
else
$(DNA): $(OBJS) $(DNA_DLL_OBJ) 
	$(LINK_DLL) $(DNA_DLL_OBJ) $(EXTRA_LIBS)
endif


veryclean: clean

clean:
	$(RM) $(OBJS)
ifeq ($(ARCH), WINNT)
	$(RM) $(DNA_DLL_OBJ)
endif
	$(RM) $(DNA)

$(OBJDEST):
	$(MKDIR) $(OBJDEST)

$(LIBDIR):
	$(MKDIR) $(LIBDIR)

Attachment: addentries.sh
Description: application/shellscript

Attachment: config.sh
Description: application/shellscript

uid=add_has_magic_number, dc=example, dc=com
uid=add_second_has_magic_number, dc=example, dc=com
uid=no_uid_number, dc=example, dc=com
uid=add_has_uid_number_550, dc=example, dc=com
uid=add_is_sub, ou=sub, dc=example, dc=com
ou=sub, dc=example, dc=com

Attachment: delentries.sh
Description: application/shellscript

/** BEGIN COPYRIGHT BLOCK
 * This Program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; version 2 of the License.
 * 
 * This Program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA.
 * 
 * In addition, as a special exception, Red Hat, Inc. gives You the additional
 * right to link the code of this Program with code not covered under the GNU
 * General Public License ("Non-GPL Code") and to distribute linked combinations
 * including the two, subject to the limitations in this paragraph. Non-GPL Code
 * permitted under this exception must only link to the code of this Program
 * through those well defined interfaces identified in the file named EXCEPTION
 * found in the source code files (the "Approved Interfaces"). The files of
 * Non-GPL Code may instantiate templates or use macros or inline functions from
 * the Approved Interfaces without causing the resulting work to be covered by
 * the GNU General Public License. Only Red Hat, Inc. may make changes or
 * additions to the list of Approved Interfaces. You must obey the GNU General
 * Public License in all respects for all of the Program code and other code used
 * in conjunction with the Program except the Non-GPL Code covered by this
 * exception. If you modify this file, you may extend this exception to your
 * version of the file, but you are not obligated to do so. If you do not wish to
 * provide this exception without modification, you must delete this exception
 * statement from your version and license this file solely under the GPL without
 * exception. 
 * 
 * 
 * Copyright (C) 2007 Red Hat, Inc.
 * All rights reserved.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif


/**
 * Distributed Numeric Assignment plug-in 
 */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "portable.h"
#include "nspr.h"
#include "slapi-private.h"
#include "dirlite_strings.h"
#include "dirver.h"
#include "prclist.h"
#include "ldif.h"

/* get file mode flags for unix */
#ifndef _WIN32
#include <sys/stat.h>
#endif

#define DNA_PLUGIN_SUBSYSTEM			"dna-plugin"
#define DNA_PLUGIN_VERSION				0x00010000

#define DNA_DN							"cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config" /* temporary */

#define DNA_SUCCESS					0
#define DNA_FAILURE					-1

/**
 * DNA config types
 */
#define DNA_TYPE	"dnaType"
#define DNA_PREFIX	"dnaPrefix"
#define DNA_NEXTVAL	"dnaNextValue"
#define DNA_INTERVAL	"dnaInterval"
#define DNA_GENERATE	"dnaMagicRegen"
#define DNA_FILTER	"dnaFilter"
#define DNA_SCOPE	"dnaScope"

#define FEATURE_DESC	"Distributed Numeric Assignment"
#define PLUGIN_DESC	"Distributed Numeric Assignment plugin"

static Slapi_PluginDesc pdesc = { 	FEATURE_DESC,
				  	PLUGIN_MAGIC_VENDOR_STR,
				  	PRODUCTTEXT,
					PLUGIN_DESC };


/**
 * linked list of config entries
 */

struct _defs {
	PRCList list;
	char *dn;
	char *type;
	char *prefix;
	int nextval;
	int interval;
	struct slapi_filter *filter;
	char *generate;
	char *scope;
} dna_anchor;
typedef struct _defs configEntry;
static PRCList *config;
static PRRWLock *g_dna_cache_lock;

static void *_PluginID					= NULL;
static char *_PluginDN					= NULL;



/**
 *	
 * DNA plug-in management functions
 *
 */
int dna_init(Slapi_PBlock *pb); 
static int dna_start(Slapi_PBlock *pb);
static int dna_close(Slapi_PBlock *pb);
static int dna_postop_init(Slapi_PBlock *pb);

/**
 *	
 * Local operation functions
 *
 */
static int loadPluginConfig();
static int parseConfigEntry(Slapi_Entry *e);
static void deleteConfig();
static void freeConfigEntry(configEntry **entry);

/**
 *
 * helpers
 *
 */
static char *dna_get_dn(Slapi_PBlock *pb);
static int dna_dn_is_config(char *dn);
static int dna_get_next_value(configEntry *config_entry, char **next_value_ret);

/**
 *
 * the ops (where the real work is done)
 *
 */
static int dna_config_check_post_op(Slapi_PBlock *pb);
static int dna_pre_op( Slapi_PBlock *pb, int modtype );
static int dna_mod_pre_op( Slapi_PBlock *pb );
static int dna_add_pre_op( Slapi_PBlock *pb );

/**
 * debug functions - global, for the debugger
 */
void dnaDumpConfig();
void dnaDumpConfigEntry(configEntry *);

/**
 * set the debug level
 */
#ifdef _WIN32
int *module_ldap_debug = 0;

void plugin_init_debug_level(int *level_ptr)
{
	module_ldap_debug = level_ptr;
}
#endif

/**
 *
 * Deal with cache locking
 *
 */
void dna_read_lock()
{
        PR_RWLock_Rlock(g_dna_cache_lock);
}

void dna_write_lock()
{
        PR_RWLock_Wlock(g_dna_cache_lock);
}

void dna_unlock()
{
        PR_RWLock_Unlock(g_dna_cache_lock);
}

/**
 *	
 * Get the dna plug-in version
 *
 */
int dna_version()
{
	return DNA_PLUGIN_VERSION;
}

/**
 * Plugin identity mgmt
 */
void setPluginID(void * pluginID) 
{
	_PluginID=pluginID;
}

void * getPluginID()
{
	return _PluginID;
}

void setPluginDN(char *pluginDN)
{
	_PluginDN = pluginDN;
}

char * getPluginDN()
{
	return _PluginDN;
}

/* 
	dna_init
	-------------
	adds our callbacks to the list
*/
int dna_init( Slapi_PBlock *pb )
{
	int status = DNA_SUCCESS;
	char * plugin_identity=NULL;

	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_init\n");

	/**
	 * Store the plugin identity for later use.
	 * Used for internal operations
	 */
	
    slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
    PR_ASSERT (plugin_identity);
	setPluginID(plugin_identity);

	if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
	    	SLAPI_PLUGIN_VERSION_01 ) != 0 ||
		slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
        	     (void *) dna_start ) != 0 ||
	    	slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
        	     (void *) dna_close ) != 0 ||
		slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
             		(void *)&pdesc ) != 0 ||
	        slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
        	         (void *) dna_mod_pre_op ) != 0 ||
	        slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
        	         (void *) dna_add_pre_op ) != 0 ||
		/* the config change checking post op */
		slapi_register_plugin(
			"postoperation",	/* op type */
			1, 			/* Enabled */ 
			"dna_init", 		/* this function desc */
			dna_postop_init,	/* init func for post op */ 
			PLUGIN_DESC,		/* plugin desc */
			NULL,			/* ? */
			plugin_identity		/* access control */
			)
	)
	{
		slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
                     "dna_init: failed to register plugin\n" );
		status = DNA_FAILURE;
	}

	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_init\n");
    return status;
}


static int dna_postop_init(Slapi_PBlock *pb)
{
        int status = DNA_SUCCESS;

        if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
                SLAPI_PLUGIN_VERSION_01 ) != 0 ||
                slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
                        (void *)&pdesc ) != 0 ||
                slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN,
                         (void *) dna_config_check_post_op ) != 0 ||
                slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
                         (void *) dna_config_check_post_op ) != 0 ||
                slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
                         (void *) dna_config_check_post_op ) != 0 ||
                slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
                         (void *) dna_config_check_post_op ) != 0
		)
        {
                slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
                     "dna_postop_init: failed to register plugin\n" );
                status = DNA_FAILURE;
        }

	return status;
}

/*
	dna_start
	--------------
	Kicks off the config cache.
	It is called after dna_init.
*/
static int dna_start( Slapi_PBlock *pb )
{
	char * plugindn = NULL;
	char * httpRootDir = NULL;

	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_start\n");

	config = &dna_anchor.list;
        g_dna_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "dna");


	/**
	 *	Get the plug-in target dn from the system
	 *	and store it for future use. This should avoid 
	 *	hardcoding of DN's in the code. 
	 */
	slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn);
	if (plugindn == NULL || strlen(plugindn) == 0)
	{
		slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , 
			"dna_start: had to use hard coded config dn\n");
		plugindn = DNA_DN;
	}
	else
	{
		slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM ,
                        "dna_start: config at %s\n", plugindn);

	}

	setPluginDN(plugindn);	

	/**
	 * Load the config for our plug-in
	 */
	PR_INIT_CLIST(config);
	if (loadPluginConfig() != DNA_SUCCESS)
	{
		slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
    	   "dna_start: unable to load plug-in configuration\n" );
		return DNA_FAILURE;
	}

	slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , "dna: ready for service\n");
	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_start\n");

	return DNA_SUCCESS;
}

/*
	dna_close
	--------------
	closes down the cache
*/
static int dna_close( Slapi_PBlock *pb )
{
	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_close\n");

	deleteConfig();

	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_close\n");

	return DNA_SUCCESS;
}

/* 
 * config looks like this
 * - cn=myplugin
 * --- ou=posix
 * ------ cn=accounts
 * ------ cn=groups
 * --- cn=samba
 * --- cn=etc
 * ------ cn=etc etc
 */
static int loadPluginConfig()
{
	int status = DNA_SUCCESS;
	int result;
	int i;
	Slapi_PBlock *search_pb;
	Slapi_Entry **entries = NULL;

	slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> loadPluginConfig\n");

	dna_write_lock();
	deleteConfig();

	search_pb = slapi_pblock_new();

	slapi_search_internal_set_pb(search_pb, DNA_DN, LDAP_SCOPE_SUBTREE,
		"objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0);
	slapi_search_internal_pb(search_pb);
	slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);

	if (status != DNA_SUCCESS)
	{
		status = DNA_SUCCESS;
		goto cleanup;
	}
	
	slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
	if (NULL == entries || entries[0] == NULL)
	{
		status = DNA_SUCCESS;
		goto cleanup;
	}

	for (i = 0; (entries[i] != NULL); i++)
	{
		status = parseConfigEntry(entries[i]);
	}

cleanup:
    	slapi_free_search_results_internal(search_pb);
    	slapi_pblock_destroy(search_pb);
	dna_unlock();
        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- loadPluginConfig\n");

	return status;
}

static int parseConfigEntry(Slapi_Entry *e)
{
	char *key			= NULL;
	char *value			= NULL;
	configEntry *entry	= NULL;
	configEntry *config_entry = NULL;
	Slapi_Attr *attr	= NULL;
	PRCList *list = NULL;
	int entry_added = 0;

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> parseConfigEntry\n");

	entry = (configEntry*) slapi_ch_calloc(1, sizeof(configEntry));
	if(0 == entry)
		goto bail;

        value = slapi_entry_get_ndn(e);
        if(value) {
                entry->dn = strdup(value);
        }

        slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dn [%s] \n",entry->dn,0,0);

	value = slapi_entry_attr_get_charptr(e, DNA_TYPE);
	if(value) {
		entry->type = value;
	}
	else
		goto bail;

	slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaType [%s] \n",entry->type,0,0);

	value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL);
	if (value) {
		entry->nextval = atoi(value);
		slapi_ch_free_string(&value);
		value = 0;
	}
	else
		goto bail;

	slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaNextValue [%d] \n",entry->nextval,0,0);

	value = slapi_entry_attr_get_charptr(e, DNA_PREFIX);
	if (value) {
		entry->prefix = value;
	}

	slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaPrefix [%s] \n",entry->prefix,0,0);

	value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL);
	if (value) {
		entry->interval = atoi(value);
		slapi_ch_free_string(&value);
		value = 0;
	}
	else
		goto bail;

	slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaInterval [%s] \n",value,0,0);

	value = slapi_entry_attr_get_charptr(e, DNA_GENERATE);
	if (value) {
		entry->generate = value;
	}

	slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaMagicRegen [%s] \n",entry->generate,0,0);

	value = slapi_entry_attr_get_charptr(e, DNA_FILTER);
	if (value) {
		entry->filter = slapi_str2filter(value);
	}
	else
		goto bail;

	slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaFilter [%s] \n",value,0,0);

	slapi_ch_free_string(&value);
	value = 0;

        value = slapi_entry_attr_get_charptr(e, DNA_SCOPE);
        if (value) {
		char *canonical_dn = slapi_dn_normalize(value);
                entry->scope = canonical_dn;
        }

        slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaScope [%s] \n",entry->scope,0,0);


	/**
	 * Finally add the entry to the list
	 * we group by type then by filter
	 * and finally sort by dn length with longer dn's
	 * first - this allows the scope checking
	 * code to be simple and quick and
	 * cunningly linear
	 */
        if(!PR_CLIST_IS_EMPTY(config))
        {
                list = PR_LIST_HEAD(config);
                while(list != config)
                {
                        config_entry = (configEntry*)list;
			
			if(slapi_attr_type_cmp(config_entry->type, entry->type,1))
				goto next;

			if(slapi_filter_compare(config_entry->filter, entry->filter))
				goto next;

			if(slapi_dn_issuffix(entry->scope,config_entry->scope))
			{
				PR_INSERT_BEFORE(&(entry->list), list);
				slapi_log_error( SLAPI_LOG_CONFIG,
					DNA_PLUGIN_SUBSYSTEM , 
					"store [%s] before [%s] \n",entry->scope,config_entry->scope,0);
				entry_added = 1;
				break;
			}

next:
                        list = PR_NEXT_LINK (list);

			if(config == list)
			{
				/* add to tail */
				PR_INSERT_BEFORE(&(entry->list), list);
				slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "store [%s] at tail\n",entry->scope,0,0);
				entry_added = 1;
				break;
			}
                }
	}
	else
	{
		/* first entry */
		PR_INSERT_LINK(&(entry->list), config);
                slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "store [%s] at head \n",entry->scope,0,0);
		entry_added = 1;
	}

bail:
	if(0 == entry_added)
	{
		slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM ,
			"config entry [%s] skipped\n",entry->dn,0,0);
		freeConfigEntry(&entry);	
	}

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- parseConfigEntry\n");

	return DNA_SUCCESS;
}

static void freeConfigEntry(configEntry **entry)
{
	configEntry *e = *entry;

	if(e->dn)
	{
		slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM ,
                        "freeing config entry [%s]\n",e->dn,0,0);
		slapi_ch_free_string(&e->dn);
	}

        if(e->type)
		slapi_ch_free_string(&e->type);

        if(e->prefix)
		slapi_ch_free_string(&e->prefix);

        if(e->filter)
		slapi_filter_free(e->filter,1);

        if(e->generate)
		slapi_ch_free_string(&e->generate);

        if(e->scope)
		slapi_ch_free_string(&e->scope);

	slapi_ch_free((void**)entry);
}

static void deleteConfigEntry(PRCList *entry)
{
	PR_REMOVE_LINK(entry);
	freeConfigEntry((configEntry**)&entry);
}

static void deleteConfig()
{
	PRCList *list;

	while(!PR_CLIST_IS_EMPTY(config))
	{
		list = PR_LIST_HEAD(config);
		deleteConfigEntry(list);
	}

	return;
}


/****************************************************
	Helpers
****************************************************/

static char *dna_get_dn(Slapi_PBlock *pb)
{
	char *dn = 0;
        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_get_dn\n");

        if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn ))
        {
                slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_get_dn: failed to get dn of changed entry");
                goto bail;
        }

        slapi_dn_normalize( dn );

bail:
        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_get_dn\n");

	return dn;
}

/* config check
        matching config dn or a descendent reloads config
*/
static int dna_dn_is_config(char *dn)
{
        int ret = 0;

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_is_config\n");

        if(slapi_dn_issuffix(dn, getPluginDN()))
        {
                ret=1;
        }

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_is_config\n");

        return ret;
}


/****************************************************
        Functions that actually do things other
        than config and startup
****************************************************/


/*
 * Perform ldap operationally atomic increment
 * Return the next value to be assigned
 * Method:
 * 1. retrieve entry
 * 2. remove current value, add new value in one operation
 * 3. if failed, and less than 3 times, goto 1
 */
static int dna_get_next_value(configEntry *config_entry, char **next_value_ret)
{
	int ret = -1;
	Slapi_DN *dn = 0;
	char *attrlist[2];
	Slapi_Entry *e = 0;
	int attempts = 0;

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_get_next_value\n");

	/* get pre-requisites to search */
	dn = slapi_sdn_new_dn_byref(config_entry->dn);
	attrlist[0] = DNA_NEXTVAL;
	attrlist[1] = 0;


	while(attempts < 3)
	{
		attempts++;

		/* do update */
	        if(e)
		{
	                slapi_entry_free(e);
			e = 0;
		}

		ret = slapi_search_internal_get_entry( dn, attrlist, &e,getPluginID());
		if(LDAP_SUCCESS == ret)
		{
			char *old_value;
						
			old_value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL);
			if(old_value)
			{
			        LDAPMod mod_add;
				LDAPMod mod_delete;
        			LDAPMod *mods[3];
				Slapi_PBlock *pb = slapi_pblock_new();
				char *delete_val[2];
				char *add_val[2];
                                char new_value[16];

				mods[0] = &mod_delete;
				mods[1] = &mod_add;
				mods[2] = 0;

				if(0 == pb)
					goto bail;

				/* perform increment */

				sprintf(new_value, "%d",
					config_entry->interval +
					atoi(old_value));

				delete_val[0] = old_value;
				delete_val[1] = 0;
				
				mod_delete.mod_op = LDAP_MOD_DELETE;
				mod_delete.mod_type = DNA_NEXTVAL;
				mod_delete.mod_values = delete_val;

                                add_val[0] = new_value;
                                add_val[1] = 0;

                                mod_add.mod_op = LDAP_MOD_ADD;
                                mod_add.mod_type = DNA_NEXTVAL;
                                mod_add.mod_values = add_val;


				mods[0] = &mod_delete;
				mods[1] = &mod_add;
				mods[2] = 0; 
		
				slapi_modify_internal_set_pb(
					pb, config_entry->dn,
        				mods, 0, 0,
        				getPluginID(), 0);

				slapi_modify_internal_pb(pb);

				slapi_pblock_get(pb,
					SLAPI_PLUGIN_INTOP_RESULT,
					&ret);

				slapi_pblock_destroy(pb);

				if(LDAP_SUCCESS == ret)
				{
					*next_value_ret = old_value;
					break;
        			}
				else
                                	slapi_ch_free((void**)&old_value);
			}
			else
				break;
		}
		else
			break;
	}

bail:
	if(dn)
		slapi_sdn_free(&dn);

	if(e)
		slapi_entry_free(e);

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_get_next_value\n");

	return ret;
}

/* for mods and adds:
	where dn's are supplied, the closest in scope
	is used as long as the type and filter
	are identical - otherwise all matches count
*/

static int dna_pre_op(Slapi_PBlock *pb,  int modtype)
{
        char *dn = 0;
        PRCList *list = 0;
        configEntry *config_entry = 0;
        struct slapi_entry *e = 0;
        char *last_type = 0;
        char *value = 0;
        int generate = 0;
	Slapi_Mods *smods = 0;
	Slapi_Mod *smod = 0;
	LDAPMod **mods;
 	int free_entry = 0;

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_pre_op\n");

        if(0 == (dn = dna_get_dn(pb)))
                goto bail;

        if(dna_dn_is_config(dn))
                goto bail;

	if(LDAP_CHANGETYPE_ADD == modtype)
	{
		slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e);
	}
	else
	{
		/* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be
		 * available but it turns out that is only true if you are
		 * a dbm backend pre-op plugin - lucky dbm backend pre-op
		 * plugins.
		 * I think that is wrong since the entry is useful for filter
		 * tests and schema checks and this plugin shouldn't be limited
		 * to a single backend type, but I don't want that fight right
		 * now so we go get the entry here
		 *
        	slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e);
		*/
		Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(dn);
		if(tmp_dn)
		{
			slapi_search_internal_get_entry( 
				tmp_dn, 0, &e,getPluginID());
			slapi_sdn_free(&tmp_dn);
			free_entry = 1;
		}

		/* grab the mods - we'll put them back later with
		 * our modifications appended
		 */	
		slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods);
		smods = slapi_mods_new();
		slapi_mods_init_passin(smods, mods);
	}

	if(0 == e)
		goto bailmod;

        dna_read_lock();

        if(!PR_CLIST_IS_EMPTY(config))
        {
                list = PR_LIST_HEAD(config);

                while(list != config)
                {
                        config_entry = (configEntry*)list;

                        /* did we already service this type? */
                        if(last_type)
                        {
                                if(!slapi_attr_type_cmp(config_entry->type, last_type,1))
                                        goto next;
                        }

                        /* is the entry in scope? */
                        if(config_entry->scope)
                        {
                                if(!slapi_dn_issuffix(dn, config_entry->scope))
                                        goto next;
                        }

                        /* does the entry match the filter? */
                        if(config_entry->filter)
                        {
                                if(LDAP_SUCCESS != slapi_vattr_filter_test(pb,
                                        e,
                                        config_entry->filter,0))
                                        goto next;
                        }


                        if(LDAP_CHANGETYPE_ADD == modtype)
                        {
                                /* does attribute contain the magic value
				   or is the type not there?
				*/
				value = slapi_entry_attr_get_charptr(
					e, config_entry->type);
                                if((value &&
					!slapi_utf8casecmp(
						config_entry->generate, 
						value)) ||
					0 == value)
                                {
                                        generate = 1;
                                }
                        }
                        else
                        {
				/* check mods for magic value */
				Slapi_Mod *next_mod = slapi_mod_new();
				smod = slapi_mods_get_first_smod(
					smods,
					next_mod);
				while(smod)
				{
					char *type = (char *) 
						slapi_mod_get_type(smod);
					
					if(slapi_attr_types_equivalent(
						type,
						config_entry->type))
					{
						struct berval *bv = 
						slapi_mod_get_first_value(
								smod);
						int len = strlen(
							config_entry->
							generate);

							
						if(len == bv->bv_len)
						{
							if(!slapi_utf8ncasecmp(
								bv->bv_val,
								config_entry->
								generate,
								len))

							generate = 1;
							break;
						}
					}

					slapi_mod_done(next_mod);
					smod = slapi_mods_get_next_smod(
						smods,
						next_mod);
				}

				slapi_mod_free(&next_mod);
                        }

                        if(generate)
                        {
                                char *new_value;
                                int len;
                                int ret = 0;

                                /* create the value to add */
                                if(dna_get_next_value(config_entry,&value))
                                        break;

                                len = strlen(value) + 1;
                                if(config_entry->prefix)
                                {
                                        len += strlen(config_entry->prefix);
                                }

                                new_value = slapi_ch_malloc(len);

                                if(config_entry->prefix)
                                {
                                        strcpy(new_value,
                                                config_entry->prefix);
                                        strcat(new_value, value);
                                }
                                else
                                        strcpy(new_value, value);

                                /* do the mod */
				if(LDAP_CHANGETYPE_ADD == modtype)
				{
					/* add - add to entry */
					slapi_entry_attr_set_charptr(
						e,
						config_entry->type,
						new_value);
				}
				else
				{
					/* mod - add to mods */
					slapi_mods_add_string(
						smods,
						LDAP_MOD_REPLACE,
						config_entry->type,
						new_value);
				}

				/* free up */
                                slapi_ch_free_string(&value);
                                slapi_ch_free_string(&new_value);

                                /* make sure we don't generate for this 
				 * type again
				 */
                                if(LDAP_SUCCESS == ret)
                                {
                                        last_type = config_entry->type;
                                }

                                generate = 0;
                        }
next:
                        list = PR_NEXT_LINK (list);
                }
        }

        dna_unlock();

bailmod:
        if(LDAP_CHANGETYPE_MODIFY == modtype)
	{
		/* these are the mods you made, really, 
		 * I didn't change them, honest, just had a quick look
		 */
		mods = slapi_mods_get_ldapmods_passout(smods);
		slapi_pblock_set( pb, SLAPI_MODIFY_MODS, mods);
		slapi_mods_free(&smods);
	}

bail:

	if(free_entry && e)
		slapi_entry_free(e);

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_pre_op\n");

        return 0;
}


static int dna_add_pre_op( Slapi_PBlock *pb )
{
        return dna_pre_op(pb, LDAP_CHANGETYPE_ADD);
}

static int dna_mod_pre_op( Slapi_PBlock *pb )
{
        return dna_pre_op(pb, LDAP_CHANGETYPE_MODIFY);
}

static int dna_config_check_post_op(Slapi_PBlock *pb)
{
        char *dn;

        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_config_check_post_op\n");

        if(dn = dna_get_dn(pb))
	{
		if(dna_dn_is_config(dn))
			loadPluginConfig();
	}

bail:
        slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_config_check_post_op\n");

	return 0;
}

/****************************************************
	End of
	Functions that actually do things other
	than config and startup
****************************************************/

/**
 * debug functions to print config
 */
void dnaDumpConfig()
{
	PRCList *list;

	dna_read_lock();

	if(!PR_CLIST_IS_EMPTY(config))
	{
		list = PR_LIST_HEAD(config);
		while(list != config)
		{
			dnaDumpConfigEntry((configEntry*)list);
			list = PR_NEXT_LINK (list);
		}
	}				

	dna_unlock();
}


void dnaDumpConfigEntry(configEntry *entry)
{
	printf("<- type --------------> %s\n", entry->type);
	printf("<---- prefix ---------> %s\n", entry->prefix);
	printf("<---- next value -----> %d\n", entry->nextval);
	printf("<---- interval -------> %d\n", entry->interval);
	printf("<---- filter ---------> %s\n", entry->filter);
	printf("<---- generate flag --> %s\n", entry->generate);
}


# --- BEGIN COPYRIGHT BLOCK ---
# This Program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 2 of the License.
# 
# This Program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA.
# 
# In addition, as a special exception, Red Hat, Inc. gives You the additional
# right to link the code of this Program with code not covered under the GNU
# General Public License ("Non-GPL Code") and to distribute linked combinations
# including the two, subject to the limitations in this paragraph. Non-GPL Code
# permitted under this exception must only link to the code of this Program
# through those well defined interfaces identified in the file named EXCEPTION
# found in the source code files (the "Approved Interfaces"). The files of
# Non-GPL Code may instantiate templates or use macros or inline functions from
# the Approved Interfaces without causing the resulting work to be covered by
# the GNU General Public License. Only Red Hat, Inc. may make changes or
# additions to the list of Approved Interfaces. You must obey the GNU General
# Public License in all respects for all of the Program code and other code used
# in conjunction with the Program except the Non-GPL Code covered by this
# exception. If you modify this file, you may extend this exception to your
# version of the file, but you are not obligated to do so. If you do not wish to
# provide this exception without modification, you must delete this exception
# statement from your version and license this file solely under the GPL without
# exception. 
# 
# 
# Copyright (C) 2007 Red Hat, Inc.
# All rights reserved.
# --- END COPYRIGHT BLOCK ---

# plugin configuration entry
dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
objectclass: nsContainer
cn: Distributed Numeric Assignment Plugin
nsslapd-plugininitfunc: dna_init
nsslapd-plugintype: preoperation
nsslapd-pluginenabled: on
nsslapd-plugindescription: Distributed Numeric Assignment plugin
nsslapd-pluginvendor: Fedora Project
nsslapd-pluginVersion: 1.1
nsslapd-pluginId: distributed-numeric-assignment
nsslapd-pluginPath: /home/prowley/srv/lib/fedora-ds/plugins/libdna-plugin.so


Attachment: editentries.sh
Description: application/shellscript

Attachment: oneentry.sh
Description: application/shellscript

# --- BEGIN COPYRIGHT BLOCK ---
# This Program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 2 of the License.
# 
# This Program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA.
# 
# In addition, as a special exception, Red Hat, Inc. gives You the additional
# right to link the code of this Program with code not covered under the GNU
# General Public License ("Non-GPL Code") and to distribute linked combinations
# including the two, subject to the limitations in this paragraph. Non-GPL Code
# permitted under this exception must only link to the code of this Program
# through those well defined interfaces identified in the file named EXCEPTION
# found in the source code files (the "Approved Interfaces"). The files of
# Non-GPL Code may instantiate templates or use macros or inline functions from
# the Approved Interfaces without causing the resulting work to be covered by
# the GNU General Public License. Only Red Hat, Inc. may make changes or
# additions to the list of Approved Interfaces. You must obey the GNU General
# Public License in all respects for all of the Program code and other code used
# in conjunction with the Program except the Non-GPL Code covered by this
# exception. If you modify this file, you may extend this exception to your
# version of the file, but you are not obligated to do so. If you do not wish to
# provide this exception without modification, you must delete this exception
# statement from your version and license this file solely under the GPL without
# exception. 
# 
# 
# Copyright (C) 2007 Red Hat, Inc.
# All rights reserved.
# --- END COPYRIGHT BLOCK ---

# add plugin configuration for posix users

dn: cn=Posix,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
objectclass: top
objectclass: nsContainer
objectclass: extensibleObject
cn: Posix

dn: cn=Accounts,cn=Posix,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
objectclass: top
objectclass: extensibleObject
cn: Accounts
dnaType: uidNumber
dnaNextValue: 500
dnaInterval: 4
dnaMagicRegen: 499
dnaFilter: (objectclass=posixAccount)

# add plugin configuration for posix groups

dn: cn=Groups,cn=Posix,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
objectclass: top
objectclass: extensibleObject
cn: Groups
dnaType: gidNumber
dnaNextValue: 500
dnaInterval: 4
dnaMagicRegen: 499
dnaFilter: (objectclass=posixGroup)

dn: uid=add_has_magic_number, dc=example, dc=com
objectclass: top
objectclass: organizationalperson
objectclass: posixaccount
cn: 1
sn: test
uid: add_has_uid_number
uidNumber: 499
gidNumber: 550
homeDirectory: /
dn: uid=add_has_magic_number, dc=example, dc=com
objectclass: top
objectclass: organizationalperson
objectclass: posixaccount
cn: 1
sn: test
uid: add_has_uid_number
uidNumber: 499
gidNumber: 550
homeDirectory: /

dn: uid=add_second_has_magic_number, dc=example, dc=com
objectclass: top
objectclass: organizationalperson
objectclass: posixaccount
cn: 2
sn: test
uid: add_second_has_magic_number
uidNumber: 499
gidNumber: 550
homeDirectory: /

dn: uid=no_uid_number, dc=example, dc=com
objectclass: top
objectclass: organizationalperson
objectclass: posixaccount
cn: 3
sn: test
uid: no_uid_number
gidNumber: 550
homeDirectory: /

dn: uid=add_has_uid_number_550, dc=example, dc=com
objectclass: top
objectclass: organizationalperson
objectclass: posixaccount
cn: 4
sn: test
uid: add_has_uid_number
uidNumber: 550
gidNumber: 550
homeDirectory: /

dn: ou=sub, dc=example, dc=com
objectclass: top
objectclass: organizationalunit
ou: sub

dn: uid=add_is_sub, ou=sub, dc=example, dc=com
objectclass: top
objectclass: organizationalperson
objectclass: posixaccount
cn: 4
sn: test
uidNumber: 499
gidNumber: 550
homeDirectory: /

Attachment: seeconfig.sh
Description: application/shellscript

Attachment: seeentries.sh
Description: application/shellscript

# --- BEGIN COPYRIGHT BLOCK ---
# This Program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 2 of the License.
# 
# This Program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA.
# 
# In addition, as a special exception, Red Hat, Inc. gives You the additional
# right to link the code of this Program with code not covered under the GNU
# General Public License ("Non-GPL Code") and to distribute linked combinations
# including the two, subject to the limitations in this paragraph. Non-GPL Code
# permitted under this exception must only link to the code of this Program
# through those well defined interfaces identified in the file named EXCEPTION
# found in the source code files (the "Approved Interfaces"). The files of
# Non-GPL Code may instantiate templates or use macros or inline functions from
# the Approved Interfaces without causing the resulting work to be covered by
# the GNU General Public License. Only Red Hat, Inc. may make changes or
# additions to the list of Approved Interfaces. You must obey the GNU General
# Public License in all respects for all of the Program code and other code used
# in conjunction with the Program except the Non-GPL Code covered by this
# exception. If you modify this file, you may extend this exception to your
# version of the file, but you are not obligated to do so. If you do not wish to
# provide this exception without modification, you must delete this exception
# statement from your version and license this file solely under the GPL without
# exception. 
# 
# 
# Copyright (C) 2007 Red Hat, Inc.
# All rights reserved.
# --- END COPYRIGHT BLOCK ---

# add Samba SIDs

dn: cn=Example top level,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
objectclass: top
objectclass: extensibleObject
dnaType: title
dnaPrefix: example-
dnaNextValue: 600
dnaInterval: 4
dnaMagicRegen: assign
dnaFilter: (objectclass=organizationalperson)
dnaScope: dc=example, dc=com

dn: cn=Example sub level,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
objectclass: top
objectclass: extensibleObject
dnaType: title
dnaPrefix: sub-example-
dnaNextValue: 600
dnaInterval: 4
dnaMagicRegen: assign
dnaFilter: (objectclass=organizationalperson)
dnaScope: ou=sub, dc=example, dc=com

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]