summaryrefslogtreecommitdiff
path: root/Linux-PAM/libpam_misc
diff options
context:
space:
mode:
Diffstat (limited to 'Linux-PAM/libpam_misc')
-rw-r--r--Linux-PAM/libpam_misc/Makefile107
-rw-r--r--Linux-PAM/libpam_misc/help_env.c105
-rw-r--r--Linux-PAM/libpam_misc/include/security/pam_misc.h62
-rw-r--r--Linux-PAM/libpam_misc/misc_conv.c380
-rw-r--r--Linux-PAM/libpam_misc/xstrdup.c31
5 files changed, 685 insertions, 0 deletions
diff --git a/Linux-PAM/libpam_misc/Makefile b/Linux-PAM/libpam_misc/Makefile
new file mode 100644
index 00000000..8d5a68e6
--- /dev/null
+++ b/Linux-PAM/libpam_misc/Makefile
@@ -0,0 +1,107 @@
+#
+# $Id: Makefile,v 1.1.1.2 2002/09/15 20:08:40 hartmans Exp $
+#
+
+# lots of debugging information goes to /tmp/pam-debug.log
+#MOREFLAGS += -D"DEBUG"
+
+include ../Make.Rules
+
+ifeq ($(WITH_LIBDEBUG),yes)
+ LIBNAME=libpam_miscd
+else
+ LIBNAME=libpam_misc
+endif
+VERSION=.$(MAJOR_REL)
+MODIFICATION=.$(MINOR_REL)
+
+CFLAGS += $(MOREFLAGS) $(DYNAMIC) $(STATIC)
+LINKLIBS += -L$(absolute_objdir)/libpam -lpam
+
+# dynamic library names
+
+LIBNAMED = $(LIBNAME).$(DYNTYPE)
+LIBNAMEDNAME = $(LIBNAMED)$(VERSION)
+LIBNAMEDFULL = $(LIBNAMEDNAME)$(MODIFICATION)
+
+# static library name
+
+LIBNAMEDSTATIC = $(LIBNAME).a
+
+LIBOBJECTS = help_env.o misc_conv.o
+
+ifeq ($(DYNAMIC_LIBPAM),yes)
+DLIBOBJECTS = $(addprefix dynamic/,$(LIBOBJECTS))
+endif
+
+ifeq ($(STATIC_LIBPAM),yes)
+SLIBOBJECTS = $(addprefix static/,$(LIBOBJECTS))
+endif
+
+# ---------------------------------------------
+## rules
+
+all: dirs $(LIBNAMED) $(LIBNAMEDSTATIC)
+
+dirs:
+ifeq ($(DYNAMIC_LIBPAM),yes)
+ $(MKDIR) dynamic
+endif
+ifeq ($(STATIC_LIBPAM),yes)
+ $(MKDIR) static
+endif
+
+dynamic/%.o : %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
+
+static/%.o : %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
+
+$(LIBNAMED): $(DLIBOBJECTS)
+ifeq ($(DYNAMIC_LIBPAM),yes)
+ ifeq ($(USESONAME),yes)
+ $(LD_L) $(SOSWITCH) $(LIBNAMEDNAME) -o $@ $(DLIBOBJECTS) $(MODULES) $(LINKLIBS)
+ else
+ $(LD_L) -o $@ $(DLIBOBJECTS) $(MODULES)
+ endif
+ ifeq ($(NEEDSONAME),yes)
+ rm -f $(LIBNAMEDFULL)
+ ln -s $(LIBNAMED) $(LIBNAMEDFULL)
+ rm -f $(LIBNAMEDNAME)
+ ln -s $(LIBNAMED) $(LIBNAMEDNAME)
+ endif
+endif
+
+$(LIBNAMEDSTATIC): $(SLIBOBJECTS)
+ifeq ($(STATIC_LIBPAM),yes)
+ $(AR) rc $@ $(SLIBOBJECTS) $(MODULES)
+ $(RANLIB) $@
+endif
+
+install: all
+ $(MKDIR) $(FAKEROOT)$(INCLUDED)
+ $(INSTALL) -m 644 include/security/pam_misc.h $(FAKEROOT)$(INCLUDED)
+ifeq ($(DYNAMIC_LIBPAM),yes)
+ $(MKDIR) $(FAKEROOT)$(libdir)
+ $(INSTALL) -m $(SHLIBMODE) $(LIBNAMED) $(FAKEROOT)$(libdir)/$(LIBNAMEDFULL)
+ $(LDCONFIG)
+ ifneq ($(DYNTYPE),"sl")
+ ( cd $(FAKEROOT)$(libdir) ; rm -f $(LIBNAMED) ; ln -s $(LIBNAMEDNAME) $(LIBNAMED) )
+ endif
+endif
+ifeq ($(STATIC_LIBPAM),yes)
+ $(INSTALL) -m 644 $(LIBNAMEDSTATIC) $(FAKEROOT)$(libdir)
+endif
+
+remove:
+ rm -f $(FAKEROOT)$(INCLUDED)/pam_misc.h
+ rm -f $(FAKEROOT)$(libdir)/$(LIBNAMEDFULL)
+ rm -f $(FAKEROOT)$(libdir)/$(LIBNAMED)
+ $(LDCONFIG)
+ rm -f $(FAKEROOT)$(libdir)/$(LIBNAMEDSTATIC)
+
+clean:
+ rm -f a.out core *~ static/*.o dynamic/*.o
+ rm -f *.a *.out *.o *.so ./include/security/*~
+ if [ -d dynamic ]; then rmdir dynamic ; fi
+ if [ -d static ]; then rmdir static ; fi
diff --git a/Linux-PAM/libpam_misc/help_env.c b/Linux-PAM/libpam_misc/help_env.c
new file mode 100644
index 00000000..3a342ed5
--- /dev/null
+++ b/Linux-PAM/libpam_misc/help_env.c
@@ -0,0 +1,105 @@
+/*
+ * $Id: help_env.c,v 1.1.1.1 2001/04/29 04:17:12 hartmans Exp $
+ *
+ * This file was written by Andrew G. Morgan <morgan@parc.power.net>
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <security/pam_misc.h>
+
+/*
+ * This is a useful function for dumping the Linux-PAM environment
+ * into some local memory, prior to it all getting lost when pam_end()
+ * is called.
+ *
+ * Initially it was assumed that libpam did not do this part correctly
+ * (based on a loose email definition). The X/Open XSSO spec makes it
+ * clear that this function is a duplicate of the one already in
+ * libpam and therefore unnecessary. IT WILL BE COMPLETELY REMOVED
+ * IN libpam_misc 1.0 */
+
+char **pam_misc_copy_env(pam_handle_t *pamh);
+char **pam_misc_copy_env(pam_handle_t *pamh)
+{
+ return pam_getenvlist(pamh);
+}
+
+/*
+ * This function should be used to carefully dispose of the copied
+ * environment.
+ *
+ * usage: env = pam_misc_drop_env(env);
+ */
+
+char **pam_misc_drop_env(char **dump)
+{
+ int i;
+
+ for (i=0; dump[i] != NULL; ++i) {
+ D(("dump[%d]=`%s'", i, dump[i]));
+ _pam_overwrite(dump[i]);
+ _pam_drop(dump[i]);
+ }
+ _pam_drop(dump);
+
+ return NULL;
+}
+
+/*
+ * This function takes the supplied environment and uploads it to be
+ * the PAM one.
+ */
+
+int pam_misc_paste_env(pam_handle_t *pamh, const char * const * user_env)
+{
+ for (; user_env && *user_env; ++user_env) {
+ int retval;
+
+ D(("uploading: %s", *user_env));
+ retval = pam_putenv(pamh, *user_env);
+ if (retval != PAM_SUCCESS) {
+ D(("error setting %s: %s", *user_env, pam_strerror(pamh,retval)));
+ return retval;
+ }
+ }
+ D(("done."));
+ return PAM_SUCCESS;
+}
+
+/*
+ * This is a wrapper to make pam behave in the way that setenv() does.
+ */
+
+int pam_misc_setenv(pam_handle_t *pamh, const char *name
+ , const char *value, int readonly)
+{
+ char *tmp;
+ int retval;
+
+ if (readonly) {
+ const char *etmp;
+
+ /* we check if the variable is there already */
+ etmp = pam_getenv(pamh, name);
+ if (etmp != NULL) {
+ D(("failed to set readonly variable: %s", name));
+ return PAM_PERM_DENIED; /* not allowed to overwrite */
+ }
+ }
+ tmp = malloc(2+strlen(name)+strlen(value));
+ if (tmp != NULL) {
+ sprintf(tmp,"%s=%s",name,value);
+ D(("pam_putt()ing: %s", tmp));
+ retval = pam_putenv(pamh, tmp);
+ _pam_overwrite(tmp); /* purge */
+ _pam_drop(tmp); /* forget */
+ } else {
+ D(("malloc failure"));
+ retval = PAM_BUF_ERR;
+ }
+
+ return retval;
+}
diff --git a/Linux-PAM/libpam_misc/include/security/pam_misc.h b/Linux-PAM/libpam_misc/include/security/pam_misc.h
new file mode 100644
index 00000000..e042e8e9
--- /dev/null
+++ b/Linux-PAM/libpam_misc/include/security/pam_misc.h
@@ -0,0 +1,62 @@
+/* $Id: pam_misc.h,v 1.1.1.2 2002/09/15 20:08:41 hartmans Exp $ */
+
+#ifndef __PAMMISC_H
+#define __PAMMISC_H
+
+#include <security/pam_appl.h>
+#include <security/pam_client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* include some useful macros */
+
+#include <security/_pam_macros.h>
+
+/* functions defined in pam_misc.* libraries */
+
+extern int misc_conv(int num_msg, const struct pam_message **msgm,
+ struct pam_response **response, void *appdata_ptr);
+
+#include <time.h>
+
+extern time_t pam_misc_conv_warn_time; /* time that we should warn user */
+extern time_t pam_misc_conv_die_time; /* cut-off time for input */
+extern const char *pam_misc_conv_warn_line; /* warning notice */
+extern const char *pam_misc_conv_die_line; /* cut-off remark */
+extern int pam_misc_conv_died; /* 1 = cut-off time reached (0 not) */
+extern int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p);
+extern void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p);
+/*
+ * Environment helper functions
+ */
+
+/* transcribe given environment (to pam) */
+extern int pam_misc_paste_env(pam_handle_t *pamh
+ , const char * const * user_env);
+
+/* char **pam_misc_copy_env(pam_handle_t *pamh);
+
+ This is no longer defined as a prototype because the X/Open XSSO
+ spec makes it clear that PAM's pam_getenvlist() does exactly
+ what this was needed for.
+
+ A wrapper is still provided in the pam_misc library - so that
+ legacy applications will still work. But _BE_WARNED_ it will
+ disappear by the release of libpam 1.0 . */
+
+/* delete environment as obtained from (pam_getenvlist) */
+extern char **pam_misc_drop_env(char **env);
+
+/* provide something like the POSIX setenv function for the (Linux-)PAM
+ * environment. */
+
+extern int pam_misc_setenv(pam_handle_t *pamh, const char *name
+ , const char *value, int readonly);
+
+#ifdef __cplusplus
+}
+#endif /* def __cplusplus */
+
+#endif /* ndef __PAMMISC_H */
diff --git a/Linux-PAM/libpam_misc/misc_conv.c b/Linux-PAM/libpam_misc/misc_conv.c
new file mode 100644
index 00000000..e352bb9a
--- /dev/null
+++ b/Linux-PAM/libpam_misc/misc_conv.c
@@ -0,0 +1,380 @@
+/*
+ * $Id: misc_conv.c,v 1.1.1.2 2002/09/15 20:08:40 hartmans Exp $
+ *
+ * A generic conversation function for text based applications
+ *
+ * Written by Andrew Morgan <morgan@linux.kernel.org>
+ */
+
+#include <security/_pam_aconf.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+
+#define INPUTSIZE PAM_MAX_MSG_SIZE /* maximum length of input+1 */
+#define CONV_ECHO_ON 1 /* types of echo state */
+#define CONV_ECHO_OFF 0
+
+/*
+ * external timeout definitions - these can be overriden by the
+ * application.
+ */
+
+time_t pam_misc_conv_warn_time = 0; /* time when we warn */
+time_t pam_misc_conv_die_time = 0; /* time when we timeout */
+
+const char *pam_misc_conv_warn_line = "..\a.Time is running out...\n";
+const char *pam_misc_conv_die_line = "..\a.Sorry, your time is up!\n";
+
+int pam_misc_conv_died=0; /* application can probe this for timeout */
+
+/*
+ * These functions are for binary prompt manipulation.
+ * The manner in which a binary prompt is processed is application
+ * specific, so these function pointers are provided and can be
+ * initialized by the application prior to the conversation function
+ * being used.
+ */
+
+static void pam_misc_conv_delete_binary(void *appdata,
+ pamc_bp_t *delete_me)
+{
+ PAM_BP_RENEW(delete_me, 0, 0);
+}
+
+int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p) = NULL;
+void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p)
+ = pam_misc_conv_delete_binary;
+
+/* the following code is used to get text input */
+
+static volatile int expired=0;
+
+/* return to the previous signal handling */
+static void reset_alarm(struct sigaction *o_ptr)
+{
+ (void) alarm(0); /* stop alarm clock - if still ticking */
+ (void) sigaction(SIGALRM, o_ptr, NULL);
+}
+
+/* this is where we intercept the alarm signal */
+static void time_is_up(int ignore)
+{
+ expired = 1;
+}
+
+/* set the new alarm to hit the time_is_up() function */
+static int set_alarm(int delay, struct sigaction *o_ptr)
+{
+ struct sigaction new_sig;
+
+ sigemptyset(&new_sig.sa_mask);
+ new_sig.sa_flags = 0;
+ new_sig.sa_handler = time_is_up;
+ if ( sigaction(SIGALRM, &new_sig, o_ptr) ) {
+ return 1; /* setting signal failed */
+ }
+ if ( alarm(delay) ) {
+ (void) sigaction(SIGALRM, o_ptr, NULL);
+ return 1; /* failed to set alarm */
+ }
+ return 0; /* all seems to have worked */
+}
+
+/* return the number of seconds to next alarm. 0 = no delay, -1 = expired */
+static int get_delay(void)
+{
+ time_t now;
+
+ expired = 0; /* reset flag */
+ (void) time(&now);
+
+ /* has the quit time past? */
+ if (pam_misc_conv_die_time && now >= pam_misc_conv_die_time) {
+ fprintf(stderr,"%s",pam_misc_conv_die_line);
+
+ pam_misc_conv_died = 1; /* note we do not reset the die_time */
+ return -1; /* time is up */
+ }
+
+ /* has the warning time past? */
+ if (pam_misc_conv_warn_time && now >= pam_misc_conv_warn_time) {
+ fprintf(stderr, "%s", pam_misc_conv_warn_line);
+ pam_misc_conv_warn_time = 0; /* reset warn_time */
+
+ /* indicate remaining delay - if any */
+
+ return (pam_misc_conv_die_time ? pam_misc_conv_die_time - now:0 );
+ }
+
+ /* indicate possible warning delay */
+
+ if (pam_misc_conv_warn_time)
+ return (pam_misc_conv_warn_time - now);
+ else if (pam_misc_conv_die_time)
+ return (pam_misc_conv_die_time - now);
+ else
+ return 0;
+}
+
+/* read a line of input string, giving prompt when appropriate */
+static char *read_string(int echo, const char *prompt)
+{
+ struct termios term_before, term_tmp;
+ char line[INPUTSIZE], *input;
+ struct sigaction old_sig;
+ int delay, nc, have_term=0;
+ sigset_t oset, nset;
+
+ D(("called with echo='%s', prompt='%s'.", echo ? "ON":"OFF" , prompt));
+
+ input = line;
+
+ if (isatty(STDIN_FILENO)) { /* terminal state */
+
+ /* is a terminal so record settings and flush it */
+ if ( tcgetattr(STDIN_FILENO, &term_before) != 0 ) {
+ D(("<error: failed to get terminal settings>"));
+ return NULL;
+ }
+ memcpy(&term_tmp, &term_before, sizeof(term_tmp));
+ if (!echo) {
+ term_tmp.c_lflag &= ~(ECHO);
+ }
+ have_term = 1;
+
+ /*
+ * We make a simple attempt to block TTY signals from terminating
+ * the conversation without giving PAM a chance to clean up.
+ */
+
+ sigemptyset(&nset);
+ sigaddset(&nset, SIGINT);
+ sigaddset(&nset, SIGTSTP);
+ (void) sigprocmask(SIG_BLOCK, &nset, &oset);
+
+ } else if (!echo) {
+ D(("<warning: cannot turn echo off>"));
+ }
+
+ /* set up the signal handling */
+ delay = get_delay();
+
+ /* reading the line */
+ while (delay >= 0) {
+
+ fprintf(stderr, "%s", prompt);
+ /* this may, or may not set echo off -- drop pending input */
+ if (have_term)
+ (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_tmp);
+
+ if ( delay > 0 && set_alarm(delay, &old_sig) ) {
+ D(("<failed to set alarm>"));
+ break;
+ } else {
+ nc = read(STDIN_FILENO, line, INPUTSIZE-1);
+ if (have_term) {
+ (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
+ if (!echo || expired) /* do we need a newline? */
+ fprintf(stderr,"\n");
+ }
+ if ( delay > 0 ) {
+ reset_alarm(&old_sig);
+ }
+ if (expired) {
+ delay = get_delay();
+ } else if (nc > 0) { /* we got some user input */
+ D(("we got some user input"));
+
+ if (nc > 0 && line[nc-1] == '\n') { /* <NUL> terminate */
+ line[--nc] = '\0';
+ } else {
+ if (echo) {
+ fprintf(stderr, "\n");
+ }
+ line[nc] = '\0';
+ }
+ input = x_strdup(line);
+ _pam_overwrite(line);
+
+ goto cleanexit; /* return malloc()ed string */
+
+ } else if (nc == 0) { /* Ctrl-D */
+ D(("user did not want to type anything"));
+
+ input = x_strdup("");
+ if (echo) {
+ fprintf(stderr, "\n");
+ }
+ goto cleanexit; /* return malloc()ed "" */
+ }
+ }
+ }
+
+ /* getting here implies that the timer expired */
+
+ D(("the timer appears to have expired"));
+
+ input = NULL;
+ _pam_overwrite(line);
+
+ cleanexit:
+
+ if (have_term) {
+ (void) sigprocmask(SIG_SETMASK, &oset, NULL);
+ (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
+ }
+
+ D(("returning [%s]", input));
+
+ return input;
+}
+
+/* end of read_string functions */
+
+/*
+ * This conversation function is supposed to be a generic PAM one.
+ * Unfortunately, it is _not_ completely compatible with the Solaris PAM
+ * codebase.
+ *
+ * Namely, for msgm's that contain multiple prompts, this function
+ * interprets "const struct pam_message **msgm" as equivalent to
+ * "const struct pam_message *msgm[]". The Solaris module
+ * implementation interprets the **msgm object as a pointer to a
+ * pointer to an array of "struct pam_message" objects (that is, a
+ * confusing amount of pointer indirection).
+ */
+
+int misc_conv(int num_msg, const struct pam_message **msgm,
+ struct pam_response **response, void *appdata_ptr)
+{
+ int count=0;
+ struct pam_response *reply;
+
+ if (num_msg <= 0)
+ return PAM_CONV_ERR;
+
+ D(("allocating empty response structure array."));
+
+ reply = (struct pam_response *) calloc(num_msg,
+ sizeof(struct pam_response));
+ if (reply == NULL) {
+ D(("no memory for responses"));
+ return PAM_CONV_ERR;
+ }
+
+ D(("entering conversation function."));
+
+ for (count=0; count < num_msg; ++count) {
+ char *string=NULL;
+
+ switch (msgm[count]->msg_style) {
+ case PAM_PROMPT_ECHO_OFF:
+ string = read_string(CONV_ECHO_OFF,msgm[count]->msg);
+ if (string == NULL) {
+ goto failed_conversation;
+ }
+ break;
+ case PAM_PROMPT_ECHO_ON:
+ string = read_string(CONV_ECHO_ON,msgm[count]->msg);
+ if (string == NULL) {
+ goto failed_conversation;
+ }
+ break;
+ case PAM_ERROR_MSG:
+ if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
+ goto failed_conversation;
+ }
+ break;
+ case PAM_TEXT_INFO:
+ if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
+ goto failed_conversation;
+ }
+ break;
+ case PAM_BINARY_PROMPT:
+ {
+ pamc_bp_t binary_prompt = NULL;
+
+ if (!msgm[count]->msg || !pam_binary_handler_fn) {
+ goto failed_conversation;
+ }
+
+ PAM_BP_RENEW(&binary_prompt,
+ PAM_BP_RCONTROL(msgm[count]->msg),
+ PAM_BP_LENGTH(msgm[count]->msg));
+ PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
+ PAM_BP_RDATA(msgm[count]->msg));
+
+ if (pam_binary_handler_fn(appdata_ptr,
+ &binary_prompt) != PAM_SUCCESS
+ || (binary_prompt == NULL)) {
+ goto failed_conversation;
+ }
+ string = (char *) binary_prompt;
+ binary_prompt = NULL;
+
+ break;
+ }
+ default:
+ fprintf(stderr, "erroneous conversation (%d)\n"
+ ,msgm[count]->msg_style);
+ goto failed_conversation;
+ }
+
+ if (string) { /* must add to reply array */
+ /* add string to list of responses */
+
+ reply[count].resp_retcode = 0;
+ reply[count].resp = string;
+ string = NULL;
+ }
+ }
+
+ *response = reply;
+ reply = NULL;
+
+ return PAM_SUCCESS;
+
+failed_conversation:
+
+ D(("the conversation failed"));
+
+ if (reply) {
+ for (count=0; count<num_msg; ++count) {
+ if (reply[count].resp == NULL) {
+ continue;
+ }
+ switch (msgm[count]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ case PAM_PROMPT_ECHO_OFF:
+ _pam_overwrite(reply[count].resp);
+ free(reply[count].resp);
+ break;
+ case PAM_BINARY_PROMPT:
+ pam_binary_handler_free(appdata_ptr,
+ (pamc_bp_t *) &reply[count].resp);
+ break;
+ case PAM_ERROR_MSG:
+ case PAM_TEXT_INFO:
+ /* should not actually be able to get here... */
+ free(reply[count].resp);
+ }
+ reply[count].resp = NULL;
+ }
+ /* forget reply too */
+ free(reply);
+ reply = NULL;
+ }
+
+ return PAM_CONV_ERR;
+}
+
diff --git a/Linux-PAM/libpam_misc/xstrdup.c b/Linux-PAM/libpam_misc/xstrdup.c
new file mode 100644
index 00000000..f5bfde66
--- /dev/null
+++ b/Linux-PAM/libpam_misc/xstrdup.c
@@ -0,0 +1,31 @@
+/* $Id: xstrdup.c,v 1.1.1.1 2001/04/29 04:17:12 hartmans Exp $ */
+
+#include <malloc.h>
+#include <string.h>
+#include <security/pam_misc.h>
+
+/*
+ * Safe duplication of character strings. "Paranoid"; don't leave
+ * evidence of old token around for later stack analysis.
+ */
+
+char *xstrdup(const char *x)
+{
+ register char *new=NULL;
+
+ if (x != NULL) {
+ register int i;
+
+ for (i=0; x[i]; ++i); /* length of string */
+ if ((new = malloc(++i)) == NULL) {
+ i = 0;
+ } else {
+ while (i-- > 0) {
+ new[i] = x[i];
+ }
+ }
+ x = NULL;
+ }
+
+ return new; /* return the duplicate or NULL on error */
+}