summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eurephiadm/CMakeLists.txt1
-rw-r--r--eurephiadm/commands.h6
-rw-r--r--eurephiadm/commands/blacklist.c317
-rw-r--r--xslt/eurephiadm/CMakeLists.txt1
-rw-r--r--xslt/eurephiadm/blacklist.xsl244
5 files changed, 569 insertions, 0 deletions
diff --git a/eurephiadm/CMakeLists.txt b/eurephiadm/CMakeLists.txt
index 0380783..a9a68eb 100644
--- a/eurephiadm/CMakeLists.txt
+++ b/eurephiadm/CMakeLists.txt
@@ -13,6 +13,7 @@ SET(efw_ipt_SRC
commands/users.c
commands/lastlog.c
commands/attempts.c
+ commands/blacklist.c
commands/certificates.c
commands/usercerts.c
commands/adminaccess.c
diff --git a/eurephiadm/commands.h b/eurephiadm/commands.h
index 1a8f67f..cd3498b 100644
--- a/eurephiadm/commands.h
+++ b/eurephiadm/commands.h
@@ -52,6 +52,9 @@ int cmd_Lastlog(eurephiaCTX *, eurephiaSESSION *, eurephiaVALUES *cfg, int argc,
void help_Attempts();
int cmd_Attempts(eurephiaCTX *, eurephiaSESSION *, eurephiaVALUES *cfg, int argc, char **argv);
+void help_Blacklist();
+int cmd_Blacklist(eurephiaCTX *, eurephiaSESSION *, eurephiaVALUES *cfg, int argc, char **argv);
+
void help_Certificates();
int cmd_Certificates(eurephiaCTX *, eurephiaSESSION *, eurephiaVALUES *cfg, int argc, char **argv);
@@ -87,6 +90,9 @@ static const eurephiadm_functions cmdline_functions[] = {
{"attempts", 1, "attempts", NULL,
"Show/edit registered login attempts", help_Attempts, cmd_Attempts},
+ {"blacklist", 1, "blacklist", NULL,
+ "Show/edit blacklisted items", help_Blacklist, cmd_Blacklist},
+
{"certs", 1, "certadmin", NULL,
"Certificate management", help_Certificates, cmd_Certificates},
diff --git a/eurephiadm/commands/blacklist.c b/eurephiadm/commands/blacklist.c
new file mode 100644
index 0000000..334e6e3
--- /dev/null
+++ b/eurephiadm/commands/blacklist.c
@@ -0,0 +1,317 @@
+/* blacklist.c -- eurephiadm blacklist command:
+ * Show/edit blacklist
+ *
+ * GPLv2 only - Copyright (C) 2009
+ * David Sommerseth <dazo@users.sourceforge.net>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_LIBXML2
+#include <libxml/tree.h>
+#endif
+
+#define MODULE "eurephia::Blacklist"
+#include <eurephia_nullsafe.h>
+#include <eurephia_context.h>
+#include <eurephia_log.h>
+#include <eurephia_xml.h>
+#include <eurephia_values_struct.h>
+#include <eurephiadb_session_struct.h>
+#include <eurephiadb_mapping.h>
+#include <eurephiadb_driver.h>
+#include <eurephia_values.h>
+
+#include "../argparser.h"
+#include "../field_print.h"
+#include "../xsltparser.h"
+
+
+void display_blacklist_help(int page)
+{
+ switch( page ) {
+ case 'l':
+ printf("The blacklist list mode will show all what are blacklisted.\n"
+ "\n"
+ " -v | --verbose Show more details\n"
+ "\n"
+ "Filters:\n"
+ " -u | --username <username> User name\n"
+ " -d | --digest <SHA1 digest> Certificate SHA1 digest\n"
+ " -i | --ipaddr <ip address> IP address\n\n");
+ break;
+
+ case 'D':
+ printf("The blacklist delete mode will remove blaclisted items.\n"
+ "\n"
+ "One of the following parameters must be given (only one):\n"
+ " -u | --username <username> User name\n"
+ " -d | --digest <SHA1 digest> Certificate SHA1 digest\n"
+ " -i | --ipaddr <ip address> IP address\n"
+ " -b | --blid <ID> Blacklist record ID\n"
+ "\n"
+ );
+ break;
+
+ case 'A':
+ printf("The blacklist mode will register an item to be blacklisted.\n"
+ "\n"
+ "One of the following parameters must be given (only one):\n"
+ " -n | --username <username> User name\n"
+ " -d | --digest <SHA1 digest> Certificate SHA1 digest\n"
+ " -i | --ipaddr <ip address> IP address\n"
+ "\n"
+ );
+ break;
+
+ default:
+ printf("Available modes for the blacklist command are:\n\n"
+ " -l | --list List all blacklisted items\n"
+ " -A | --add Add an item to the blacklist\n"
+ " -D | --delete Delete an item from the blacklist\n"
+ " -h | --help <mode> Show help\n\n");
+ break;
+ }
+}
+
+
+void help_Blacklist()
+{
+ display_blacklist_help(0);
+}
+
+int help_Blacklist2(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv)
+{
+ e_options modeargs[] = {
+ {"--list", "-l", 0},
+ {"--reset", "-R", 0},
+ {"--delete", "-D", 0},
+ {NULL, NULL, 0}
+ };
+ int i = 1;
+ display_blacklist_help(eurephia_getopt(&i, argc, argv, modeargs));
+ return 0;
+}
+
+
+int list_blacklist(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv)
+{
+ xmlDoc *blst_xml = NULL, *srch_xml = NULL;
+ xmlNode *fmap_n = NULL, *srch_n = NULL;
+ char *xsltparams[] = {"view", "'list'", NULL};
+ int i = 0;
+
+ e_options modeargs[] = {
+ {"--verbose", "-v", 0},
+ {"--help", "-h", 0},
+ {"--username", "-u", 1},
+ {"--digest", "-d", 1},
+ {"--ipaddr", "-i", 1},
+ {NULL, NULL, 0}
+ };
+
+ eurephiaXML_CreateDoc(ctx, 1, "blacklist", &srch_xml, &srch_n);
+ xmlNewProp(srch_n, (xmlChar *) "mode", (xmlChar *) "list");
+
+ fmap_n = xmlNewChild(srch_n, NULL, (xmlChar *) "fieldMapping", NULL);
+ xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *) "blacklist");
+
+ for( i = 1; i < argc; i++ ) {
+ switch( eurephia_getopt(&i, argc, argv, modeargs) ) {
+ case 'v':
+ xsltparams[1] = "'details'";
+ break;
+
+ case 'u':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "username", (xmlChar *) optargs[0]);
+ break;
+
+ case 'd':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "digest", (xmlChar *) optargs[0]);
+ break;
+
+ case 'i':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "ip", (xmlChar *) optargs[0]);
+ break;
+
+ case 'h':
+ display_blacklist_help('l');
+ return 0;
+
+ default:
+ return 1;
+ }
+ }
+
+ blst_xml = eDBadminBlacklist(ctx, srch_xml);
+ xmlFreeDoc(srch_xml);
+ if( blst_xml == NULL ) {
+ fprintf(stderr, "%s: Error retrieving the blacklist.\n", MODULE);
+ return 1;
+ }
+ xslt_print_xmldoc(stdout, cfg, blst_xml, "blacklist.xsl", (const char **) xsltparams);
+ xmlFreeDoc(blst_xml);
+ return 0;
+}
+
+
+int modify_blacklist(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv)
+{
+ xmlDoc *result_xml = NULL, *upd_xml = NULL;
+ xmlNode *fmap_n = NULL, *res_n = NULL, *upd_n = NULL;
+ int i = 0, rc = 1, mode = 0;
+
+ e_options modeargs[] = {
+ {"--help", "-h", 0},
+ {"--username", "-u", 1},
+ {"--digest", "-d", 1},
+ {"--ipaddr", "-i", 1},
+ {"--blid", "-b", 1},
+ {NULL, NULL, 0}
+ };
+
+ eurephiaXML_CreateDoc(ctx, 1, "blacklist", &upd_xml, &upd_n);
+ if( (strcmp(argv[0], "--add") == 0) || (strcmp(argv[0], "-A") == 0) ) {
+ xmlNewProp(upd_n, (xmlChar *) "mode", (xmlChar *) "add");
+ mode = 'A';
+ } else if( (strcmp(argv[0], "--delete") == 0) || (strcmp(argv[0], "-D") == 0) ) {
+ xmlNewProp(upd_n, (xmlChar *) "mode", (xmlChar *) "delete");
+ mode = 'D';
+ } else {
+ fprintf(stderr, "%s: Invalid mode\n", MODULE);
+ xmlFreeDoc(upd_xml);
+ return 1;
+ }
+
+ fmap_n = xmlNewChild(upd_n, NULL, (xmlChar *) "fieldMapping", NULL);
+ xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *) "blacklist");
+
+ for( i = 1; i < argc; i++ ) {
+ switch( eurephia_getopt(&i, argc, argv, modeargs) ) {
+ case 'u':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "username", (xmlChar *) optargs[0]);
+ break;
+
+ case 'd':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "digest", (xmlChar *) optargs[0]);
+ break;
+
+ case 'i':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "ip", (xmlChar *) optargs[0]);
+ break;
+
+ case 'b':
+ xmlNewChild(fmap_n, NULL, (xmlChar *) "id", (xmlChar *) optargs[0]);
+ break;
+
+ case 'h':
+ display_blacklist_help(mode);
+ return 0;
+
+ default:
+ return 1;
+ }
+ }
+
+ result_xml = eDBadminBlacklist(ctx, upd_xml);
+ xmlFreeDoc(upd_xml);
+ if( result_xml == NULL ) {
+ fprintf(stderr, "%s: Could not modify the blacklist\n", MODULE);
+ return 1;
+ }
+
+ res_n = eurephiaXML_getRoot(ctx, result_xml, NULL, 1);
+ if( res_n == NULL ) {
+ fprintf(stderr, "%s: Error updating the blacklist. No results returned.\n", MODULE);
+ return 1;
+
+ }
+
+ if( xmlStrcmp(res_n->name, (xmlChar *) "Error") == 0 ) {
+ fprintf(stderr, "%s: %s\n", MODULE, xmlExtractContent(res_n));
+ rc = 1;
+ } else {
+ fprintf(stdout, "%s: %s\n", MODULE, xmlExtractContent(res_n));
+ rc = 0;
+ }
+ xmlFreeDoc(result_xml);
+
+ return rc;
+}
+
+
+int cmd_Blacklist(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv)
+{
+ char **mode_argv;
+ int rc = 0, i = 0, mode_argc = 0;
+ e_options cmdargs[] = {
+ {"--list", "-l", 0},
+ {"--add", "-A", 0},
+ {"--delete", "-D", 0},
+ {"--help", "-h", 0},
+ {NULL, NULL, 0}
+ };
+ int (*mode_fnc) (eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv);
+
+ assert((ctx != NULL) && (ctx->dbc != NULL));
+
+ mode_fnc = NULL;
+ for( i = 1; i < argc; i++ ) {
+ switch( eurephia_getopt(&i, argc, argv, cmdargs) ) {
+ case 'l':
+ mode_fnc = list_blacklist;
+ break;
+
+ case 'A':
+ case 'D':
+ mode_fnc = modify_blacklist;
+ break;
+
+ case 'h':
+ mode_fnc = help_Blacklist2;
+
+ default:
+ break;
+ }
+ if( mode_fnc != NULL ) {
+ break;
+ }
+ }
+
+ // If we do not have any known mode defined, exit with error
+ if( mode_fnc == NULL ) {
+ fprintf(stderr, "%s: Unknown argument. No mode given\n", MODULE);
+ return 1;
+ }
+
+ // Allocate memory for our arguments being sent to the mode function
+ mode_argv = (char **) calloc(sizeof(char *), (argc - i)+2);
+ assert(mode_argv != NULL);
+
+ // Copy over only the arguments needed for the mode
+ mode_argc = eurephia_arraycp(i, argc, argv, mode_argv, (argc - i));
+
+ // Call the mode function
+ rc = mode_fnc(ctx, sess, cfg, mode_argc, mode_argv);
+ free_nullsafe(mode_argv);
+
+ return rc;
+
+}
diff --git a/xslt/eurephiadm/CMakeLists.txt b/xslt/eurephiadm/CMakeLists.txt
index fedce10..ff8c213 100644
--- a/xslt/eurephiadm/CMakeLists.txt
+++ b/xslt/eurephiadm/CMakeLists.txt
@@ -6,5 +6,6 @@ SET(eurephiadm_XSLT
usercerts.xsl
users.xsl
attempts.xsl
+ blacklist.xsl
)
INSTALL(FILES ${eurephiadm_XSLT} DESTINATION ${EUREPHIADM_XSLT_PATH}/)
diff --git a/xslt/eurephiadm/blacklist.xsl b/xslt/eurephiadm/blacklist.xsl
new file mode 100644
index 0000000..0e26af5
--- /dev/null
+++ b/xslt/eurephiadm/blacklist.xsl
@@ -0,0 +1,244 @@
+<?xml version="1.0"?>
+<!--
+ *
+ * GPLv2 only - Copyright (C) 2008, 2009
+ * David Sommerseth <dazo@users.sourceforge.net>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="text" encoding="UTF-8"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:template match="/eurephia">
+ <xsl:choose>
+ <xsl:when test="$view = 'list'">
+ <xsl:apply-templates select="blacklist" mode="list"/>
+ </xsl:when>
+ <xsl:when test="$view = 'details' or $view = 'details2'">
+ <xsl:apply-templates select="blacklist" mode="details"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">Invalid view: <xsl:value-of select="$view"/></xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+
+ <!-- -->
+ <!-- Simple listing -->
+ <!-- -->
+
+ <xsl:template match="blacklist" mode="list">
+ <xsl:text> ID Reference Registered&#10;</xsl:text>
+ <xsl:text>-------------------------------------------------------------------------------&#10;</xsl:text>
+ <xsl:apply-templates select="username|certificate|ipaddress" mode="list"/>
+ <xsl:text>-------------------------------------------------------------------------------&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="blacklist/username" mode="list">
+ <xsl:text> ** Username&#10;</xsl:text>
+ <xsl:apply-templates select="blacklisted" mode="list"/>
+ </xsl:template>
+
+ <xsl:template match="blacklist/username/blacklisted" mode="list">
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="@blid"/>
+ <xsl:with-param name="width" select="4"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="username"/>
+ <xsl:with-param name="width" select="59"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="registered"/>
+ <xsl:with-param name="width" select="10"/>
+ </xsl:call-template>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="blacklist/certificate" mode="list">
+ <xsl:text> ** Certificate&#10;</xsl:text>
+ <xsl:apply-templates select="blacklisted" mode="list"/>
+ </xsl:template>
+
+ <xsl:template match="blacklist/certificate/blacklisted" mode="list">
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="@blid"/>
+ <xsl:with-param name="width" select="4"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="certificate"/>
+ <xsl:with-param name="width" select="59"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="registered"/>
+ <xsl:with-param name="width" select="10"/>
+ </xsl:call-template>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="blacklist/ipaddress" mode="list">
+ <xsl:text> ** IP Address&#10;</xsl:text>
+ <xsl:apply-templates select="blacklisted" mode="list"/>
+ </xsl:template>
+
+ <xsl:template match="blacklist/ipaddress/blacklisted" mode="list">
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="@blid"/>
+ <xsl:with-param name="width" select="4"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="ipaddress"/>
+ <xsl:with-param name="width" select="59"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="registered"/>
+ <xsl:with-param name="width" select="10"/>
+ </xsl:call-template>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:template>
+
+
+ <!-- -->
+ <!-- Detailed listing -->
+ <!-- -->
+
+ <xsl:template match="blacklist" mode="details">
+ <xsl:text> ID Reference&#10;</xsl:text>
+ <xsl:text> Registered first time Last accessed&#10;</xsl:text>
+ <xsl:text>-------------------------------------------------------------------------------&#10;</xsl:text>
+ <xsl:apply-templates select="username|certificate|ipaddress" mode="details"/>
+ <xsl:text>-------------------------------------------------------------------------------&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="blacklist/username" mode="details">
+ <xsl:text> ** Username&#10;</xsl:text>
+ <xsl:apply-templates select="blacklisted" mode="details"/>
+ </xsl:template>
+
+ <xsl:template match="blacklist/username/blacklisted" mode="details">
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="@blid"/>
+ <xsl:with-param name="width" select="4"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="username"/>
+ <xsl:with-param name="width" select="59"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+ <xsl:text>&#10; </xsl:text>
+
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="registered"/>
+ <xsl:with-param name="width" select="19"/>
+ </xsl:call-template>
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="last_accessed"/>
+ <xsl:with-param name="width" select="19"/>
+ </xsl:call-template>
+ <xsl:text>&#10;&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="blacklist/certificate" mode="details">
+ <xsl:text> ** Certificate&#10;</xsl:text>
+ <xsl:apply-templates select="blacklisted" mode="details"/>
+ </xsl:template>
+
+ <xsl:template match="blacklist/certificate/blacklisted" mode="details">
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="@blid"/>
+ <xsl:with-param name="width" select="4"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="certificate"/>
+ <xsl:with-param name="width" select="59"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+ <xsl:text>&#10; </xsl:text>
+
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="registered"/>
+ <xsl:with-param name="width" select="19"/>
+ </xsl:call-template>
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="last_accessed"/>
+ <xsl:with-param name="width" select="19"/>
+ </xsl:call-template>
+ <xsl:text>&#10;&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="blacklist/ipaddress" mode="details">
+ <xsl:text> ** IP Address&#10;</xsl:text>
+ <xsl:apply-templates select="blacklisted" mode="details"/>
+ </xsl:template>
+
+ <xsl:template match="blacklist/ipaddress/blacklisted" mode="details">
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="right-align">
+ <xsl:with-param name="value" select="@blid"/>
+ <xsl:with-param name="width" select="4"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="ipaddress"/>
+ <xsl:with-param name="width" select="59"/>
+ </xsl:call-template><xsl:text> </xsl:text>
+ <xsl:text>&#10; </xsl:text>
+
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="registered"/>
+ <xsl:with-param name="width" select="19"/>
+ </xsl:call-template>
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="left-align">
+ <xsl:with-param name="value" select="last_accessed"/>
+ <xsl:with-param name="width" select="19"/>
+ </xsl:call-template>
+ <xsl:text>&#10;&#10;</xsl:text>
+ </xsl:template>
+
+
+
+
+
+ <!-- -->
+ <!-- Internal helpers -->
+ <!-- -->
+
+ <xsl:template name="left-align">
+ <xsl:param name="value"/>
+ <xsl:param name="width"/>
+ <xsl:value-of select="substring(concat($value, ' '), 1, $width)"/>
+ </xsl:template>
+
+ <xsl:template name="right-align">
+ <xsl:param name="value"/>
+ <xsl:param name="width"/>
+ <xsl:value-of select="concat(substring(' ', 1, $width - string-length($value)), substring($value, 1, $width))"/>
+ </xsl:template>
+
+</xsl:stylesheet>