]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/login.c
scriptreplay: cleanup usage()
[thirdparty/util-linux.git] / login-utils / login.c
index 2551631d30f29c29d320027b1195b0eebeed81e4..d213e4784b8bb20a8089b62f5b491df51a57c4af 100644 (file)
@@ -24,7 +24,7 @@
  * from this software without specific prior written permission.
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 #include <sys/param.h>
 #include <stdio.h>
 #include <errno.h>
 #include <grp.h>
 #include <pwd.h>
-#include <utmp.h>
+#include <utmpx.h>
+#ifdef HAVE_LASTLOG_H
+# include <lastlog.h>
+#endif
 #include <stdlib.h>
 #include <sys/syslog.h>
-#include <sys/sysmacros.h>
 #ifdef HAVE_LINUX_MAJOR_H
 # include <linux/major.h>
 #endif
 #include <netdb.h>
-#include <lastlog.h>
 #include <security/pam_appl.h>
 #ifdef HAVE_SECURITY_PAM_MISC_H
 # include <security/pam_misc.h>
 #include "pathnames.h"
 #include "strutils.h"
 #include "nls.h"
+#include "env.h"
 #include "xalloc.h"
 #include "all-io.h"
 #include "fileutils.h"
 #include "ttyutils.h"
+#include "pwdutils.h"
 
 #include "logindefs.h"
 
@@ -102,9 +105,12 @@ struct login_context {
        const char      *tty_number;    /* end of the tty_path */
        mode_t          tty_mode;       /* chmod() mode */
 
-       char            *username;      /* from command line or PAM */
+       const char      *username;      /* points to PAM, pwd or cmd_username */
+       char            *cmd_username;  /* username specified on command line */
+
 
        struct passwd   *pwd;           /* user info */
+       char            *pwdbuf;        /* pwd strings */
 
        pam_handle_t    *pamh;          /* PAM handler */
        struct pam_conv conv;           /* PAM conversation */
@@ -135,6 +141,7 @@ struct login_context {
 static unsigned int timeout = LOGIN_TIMEOUT;
 static int child_pid = 0;
 static volatile int got_sig = 0;
+static char timeout_msg[128];
 
 #ifdef LOGIN_CHOWN_VCS
 /* true if the filedescriptor fd is a console tty, very Linux specific */
@@ -170,15 +177,14 @@ timedout2(int sig __attribute__ ((__unused__)))
        tcgetattr(0, &ti);
        ti.c_lflag |= ECHO;
        tcsetattr(0, TCSANOW, &ti);
-       exit(EXIT_SUCCESS);     /* %% */
+       _exit(EXIT_SUCCESS);    /* %% */
 }
 
 static void timedout(int sig __attribute__ ((__unused__)))
 {
        signal(SIGALRM, timedout2);
        alarm(10);
-       /* TRANSLATORS: The standard value for %u is 60. */
-       warnx(_("timed out after %u seconds"), timeout);
+       ignore_result( write(STDERR_FILENO, timeout_msg, strlen(timeout_msg)) );
        signal(SIGALRM, SIG_IGN);
        alarm(0);
        timedout2(0);
@@ -349,7 +355,7 @@ static void chown_tty(struct login_context *cxt)
 }
 
 /*
- * Reads the currect terminal path and initializes cxt->tty_* variables.
+ * Reads the current terminal path and initializes cxt->tty_* variables.
  */
 static void init_tty(struct login_context *cxt)
 {
@@ -358,7 +364,7 @@ static void init_tty(struct login_context *cxt)
 
        cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE);
 
-       get_terminal_name(0, &cxt->tty_path, &cxt->tty_name, &cxt->tty_number);
+       get_terminal_name(&cxt->tty_path, &cxt->tty_name, &cxt->tty_number);
 
        /*
         * In case login is suid it was possible to use a hardlink as stdin
@@ -400,14 +406,14 @@ static void init_tty(struct login_context *cxt)
        tcsetattr(0, TCSANOW, &ttt);
 
        /*
-        * Let's close file decriptors before vhangup
+        * Let's close file descriptors before vhangup
         * https://lkml.org/lkml/2012/6/5/145
         */
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
 
-       signal(SIGHUP, SIG_IGN);        /* so vhangup() wont kill us */
+       signal(SIGHUP, SIG_IGN);        /* so vhangup() won't kill us */
        vhangup();
        signal(SIGHUP, SIG_DFL);
 
@@ -426,45 +432,35 @@ static void init_tty(struct login_context *cxt)
  */
 static void log_btmp(struct login_context *cxt)
 {
-       struct utmp ut;
-#if defined(_HAVE_UT_TV)        /* in <utmpbits.h> included by <utmp.h> */
+       struct utmpx ut;
        struct timeval tv;
-#endif
 
        memset(&ut, 0, sizeof(ut));
 
-       strncpy(ut.ut_user,
+       str2memcpy(ut.ut_user,
                cxt->username ? cxt->username : "(unknown)",
                sizeof(ut.ut_user));
 
        if (cxt->tty_number)
-               strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+               str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
        if (cxt->tty_name)
-               xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+               str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
 
-#if defined(_HAVE_UT_TV)       /* in <utmpbits.h> included by <utmp.h> */
        gettimeofday(&tv, NULL);
        ut.ut_tv.tv_sec = tv.tv_sec;
        ut.ut_tv.tv_usec = tv.tv_usec;
-#else
-       {
-               time_t t;
-               time(&t);
-               ut.ut_time = t; /* ut_time is not always a time_t */
-       }
-#endif
 
        ut.ut_type = LOGIN_PROCESS;     /* XXX doesn't matter */
        ut.ut_pid = cxt->pid;
 
        if (cxt->hostname) {
-               xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
+               str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
                if (*cxt->hostaddress)
                        memcpy(&ut.ut_addr_v6, cxt->hostaddress,
                               sizeof(ut.ut_addr_v6));
        }
 
-       updwtmp(_PATH_BTMP, &ut);
+       updwtmpx(_PATH_BTMP, &ut);
 }
 
 
@@ -507,6 +503,9 @@ static void log_lastlog(struct login_context *cxt)
        if (!cxt->pwd)
                return;
 
+       if (cxt->pwd->pw_uid > (uid_t) getlogindefs_num("LASTLOG_UID_MAX", ULONG_MAX))
+               return;
+
        /* lastlog is huge on systems with large UIDs, ignore SIGXFSZ */
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = SIG_IGN;
@@ -545,9 +544,9 @@ static void log_lastlog(struct login_context *cxt)
        ll.ll_time = t;         /* ll_time is always 32bit */
 
        if (cxt->tty_name)
-               xstrncpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
+               str2memcpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
        if (cxt->hostname)
-               xstrncpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
+               str2memcpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
 
        if (write_all(fd, (char *)&ll, sizeof(ll)))
                warn(_("write lastlog failed"));
@@ -563,12 +562,12 @@ done:
  */
 static void log_utmp(struct login_context *cxt)
 {
-       struct utmp ut;
-       struct utmp *utp;
+       struct utmpx ut;
+       struct utmpx *utp;
        struct timeval tv;
 
-       utmpname(_PATH_UTMP);
-       setutent();
+       utmpxname(_PATH_UTMP);
+       setutxent();
 
        /* Find pid in utmp.
         *
@@ -578,7 +577,7 @@ static void log_utmp(struct login_context *cxt)
         * but some number calculated from the previous and current runlevel.)
         * -- Michael Riepe <michael@stud.uni-hannover.de>
         */
-       while ((utp = getutent()))
+       while ((utp = getutxent()))
                if (utp->ut_pid == cxt->pid
                    && utp->ut_type >= INIT_PROCESS
                    && utp->ut_type <= DEAD_PROCESS)
@@ -587,19 +586,19 @@ static void log_utmp(struct login_context *cxt)
        /* If we can't find a pre-existing entry by pid, try by line.
         * BSD network daemons may rely on this. */
        if (utp == NULL && cxt->tty_name) {
-               setutent();
+               setutxent();
                ut.ut_type = LOGIN_PROCESS;
-               strncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
-               utp = getutline(&ut);
+               str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+               utp = getutxline(&ut);
        }
 
        /* If we can't find a pre-existing entry by pid and line, try it by id.
         * Very stupid telnetd daemons don't set up utmp at all. (kzak) */
        if (utp == NULL && cxt->tty_number) {
-            setutent();
+            setutxent();
             ut.ut_type = DEAD_PROCESS;
-            strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
-            utp = getutid(&ut);
+            str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+            utp = getutxid(&ut);
        }
 
        if (utp)
@@ -609,37 +608,28 @@ static void log_utmp(struct login_context *cxt)
                memset(&ut, 0, sizeof(ut));
 
        if (cxt->tty_number && ut.ut_id[0] == 0)
-               strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+               str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
        if (cxt->username)
-               strncpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
+               str2memcpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
        if (cxt->tty_name)
-               xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+               str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
 
-#ifdef _HAVE_UT_TV             /* in <utmpbits.h> included by <utmp.h> */
        gettimeofday(&tv, NULL);
        ut.ut_tv.tv_sec = tv.tv_sec;
        ut.ut_tv.tv_usec = tv.tv_usec;
-#else
-       {
-               time_t t;
-               time(&t);
-               ut.ut_time = t; /* ut_time is not always a time_t */
-                               /* glibc2 #defines it as ut_tv.tv_sec */
-       }
-#endif
        ut.ut_type = USER_PROCESS;
        ut.ut_pid = cxt->pid;
        if (cxt->hostname) {
-               xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
+               str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
                if (*cxt->hostaddress)
                        memcpy(&ut.ut_addr_v6, cxt->hostaddress,
                               sizeof(ut.ut_addr_v6));
        }
 
-       pututline(&ut);
-       endutent();
+       pututxline(&ut);
+       endutxent();
 
-       updwtmp(_PATH_WTMP, &ut);
+       updwtmpx(_PATH_WTMP, &ut);
 }
 
 static void log_syslog(struct login_context *cxt)
@@ -669,33 +659,13 @@ static void log_syslog(struct login_context *cxt)
        }
 }
 
-static struct passwd *get_passwd_entry(const char *username,
-                                        char **pwdbuf,
-                                        struct passwd *pwd)
-{
-       struct passwd *res = NULL;
-       int x;
-
-       if (!pwdbuf || !username)
-               return NULL;
-
-       *pwdbuf = xrealloc(*pwdbuf, UL_GETPW_BUFSIZ);
-
-       x = getpwnam_r(username, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res);
-       if (!res) {
-               errno = x;
-               return NULL;
-       }
-       return res;
-}
-
 /* encapsulate stupid "void **" pam_get_item() API */
-static int loginpam_get_username(pam_handle_t *pamh, char **name)
+static int loginpam_get_username(pam_handle_t *pamh, const char **name)
 {
-       const void *item = (void *)*name;
+       const void *item = (const void *)*name;
        int rc;
        rc = pam_get_item(pamh, PAM_USER, &item);
-       *name = (char *)item;
+       *name = (const char *)item;
        return rc;
 }
 
@@ -712,7 +682,8 @@ static void loginpam_err(pam_handle_t *pamh, int retcode)
 }
 
 /*
- * Composes "<host> login: " string; or returns "login: " if -H is given.
+ * Composes "<host> login: " string; or returns "login: " if -H is given or
+ * LOGIN_PLAIN_PROMPT=yes configured.
  */
 static const char *loginpam_get_prompt(struct login_context *cxt)
 {
@@ -720,11 +691,16 @@ static const char *loginpam_get_prompt(struct login_context *cxt)
        char *prompt, *dflt_prompt = _("login: ");
        size_t sz;
 
-       if (cxt->nohost || !(host = get_thishost(cxt, NULL)))
+       if (cxt->nohost)
+               return dflt_prompt;     /* -H on command line */
+
+       if (getlogindefs_bool("LOGIN_PLAIN_PROMPT", 0) == 1)
                return dflt_prompt;
 
-       sz = strlen(host) + 1 + strlen(dflt_prompt) + 1;
+       if (!(host = get_thishost(cxt, NULL)))
+               return dflt_prompt;
 
+       sz = strlen(host) + 1 + strlen(dflt_prompt) + 1;
        prompt = xmalloc(sz);
        snprintf(prompt, sz, "%s %s", host, dflt_prompt);
 
@@ -769,7 +745,6 @@ static pam_handle_t *init_loginpam(struct login_context *cxt)
                loginpam_err(pamh, rc);
 
        /* We don't need the original username. We have to follow PAM. */
-       free(cxt->username);
        cxt->username = NULL;
        cxt->pamh = pamh;
 
@@ -881,8 +856,7 @@ static void loginpam_acct(struct login_context *cxt)
 
        if (!cxt->username || !*cxt->username) {
                warnx(_("\nSession setup problem, abort."));
-               syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."),
-                      __FUNCTION__, __LINE__);
+               syslog(LOG_ERR, _("NULL user name. Abort."));
                pam_end(pamh, PAM_SYSTEM_ERR);
                sleepexit(EXIT_FAILURE);
        }
@@ -980,6 +954,8 @@ static void fork_session(struct login_context *cxt)
                close(0);
                close(1);
                close(2);
+               free_getlogindefs_data();
+
                sa.sa_handler = SIG_IGN;
                sigaction(SIGQUIT, &sa, NULL);
                sigaction(SIGINT, &sa, NULL);
@@ -1038,31 +1014,34 @@ static void init_environ(struct login_context *cxt)
 
        /* destroy environment unless user has requested preservation (-p) */
        if (!cxt->keep_env) {
-               environ = (char **) xmalloc(sizeof(char *));
+               environ = xmalloc(sizeof(char *));
                memset(environ, 0, sizeof(char *));
        }
 
-       setenv("HOME", pwd->pw_dir, 0); /* legal to override */
-       setenv("USER", pwd->pw_name, 1);
-       setenv("SHELL", pwd->pw_shell, 1);
-       setenv("TERM", termenv ? termenv : "dumb", 1);
+       xsetenv("HOME", pwd->pw_dir, 0);        /* legal to override */
+       xsetenv("USER", pwd->pw_name, 1);
+       xsetenv("SHELL", pwd->pw_shell, 1);
+       xsetenv("TERM", termenv ? termenv : "dumb", 1);
        free(termenv);
 
-       if (pwd->pw_uid)
-               logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
+       if (pwd->pw_uid) {
+               if (logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH) != 0)
+                       err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
 
-       else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0)
-               logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
+       } else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0 &&
+                  logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT) != 0) {
+                       err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
+       }
 
        /* mailx will give a funny error msg if you forget this one */
        len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
-       if (len > 0 && (size_t) len + 1 <= sizeof(tmp))
-               setenv("MAIL", tmp, 0);
+       if (len > 0 && (size_t) len < sizeof(tmp))
+               xsetenv("MAIL", tmp, 0);
 
        /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll
         * not allow modifying it.
         */
-       setenv("LOGNAME", pwd->pw_name, 1);
+       xsetenv("LOGNAME", pwd->pw_name, 1);
 
        env = pam_getenvlist(cxt->pamh);
        for (i = 0; env && env[i]; i++)
@@ -1102,13 +1081,38 @@ static void init_remote_info(struct login_context *cxt, char *remotehost)
                } else if (info->ai_family == AF_INET6) {
                        struct sockaddr_in6 *sa =
                                     (struct sockaddr_in6 *) info->ai_addr;
+#ifdef IN6_IS_ADDR_V4MAPPED
+                       if (IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr)) {
+                               const uint8_t *bytes = sa->sin6_addr.s6_addr;
+                               struct in_addr addr = { *(const in_addr_t *) (bytes + 12) };
 
-                       memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr));
+                               memcpy(cxt->hostaddress, &addr, sizeof(struct in_addr));
+                       } else
+#endif
+                               memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr));
                }
                freeaddrinfo(info);
        }
 }
 
+static void __attribute__((__noreturn__)) usage(void)
+{
+       fputs(USAGE_HEADER, stdout);
+       printf(_(" %s [-p] [-h <host>] [-H] [[-f] <username>]\n"), program_invocation_short_name);
+       fputs(USAGE_SEPARATOR, stdout);
+       fputs(_("Begin a session on the system.\n"), stdout);
+
+       fputs(USAGE_OPTIONS, stdout);
+       puts(_(" -p             do not destroy the environment"));
+       puts(_(" -f             skip a second login authentication"));
+       puts(_(" -h <host>      hostname to be used for utmp logging"));
+       puts(_(" -H             suppress hostname in the login prompt"));
+       printf("     --help     %s\n", USAGE_OPTSTR_HELP);
+       printf(" -V, --version  %s\n", USAGE_OPTSTR_VERSION);
+       printf(USAGE_MAN_TAIL("login(1)"));
+       exit(EXIT_SUCCESS);
+}
+
 int main(int argc, char **argv)
 {
        int c;
@@ -1118,9 +1122,7 @@ int main(int argc, char **argv)
        int childArgc = 0;
        int retcode;
        struct sigaction act;
-
-       char *pwdbuf = NULL;
-       struct passwd *pwd = NULL, _pwd;
+       struct passwd *pwd;
 
        struct login_context cxt = {
                .tty_mode = TTY_MODE,             /* tty chmod() */
@@ -1133,8 +1135,25 @@ int main(int argc, char **argv)
 
        };
 
+       /* the only two longopts to satisfy UL standards */
+       enum { HELP_OPTION = CHAR_MAX + 1 };
+       static const struct option longopts[] = {
+               {"help", no_argument, NULL, HELP_OPTION},
+               {"version", no_argument, NULL, 'V'},
+               {NULL, 0, NULL, 0}
+       };
+
        timeout = (unsigned int)getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT);
 
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+
+       /* TRANSLATORS: The standard value for %u is 60. */
+       snprintf(timeout_msg, sizeof(timeout_msg),
+           _("%s: timed out after %u seconds"),
+           program_invocation_short_name, timeout);
+
        signal(SIGALRM, timedout);
        (void) sigaction(SIGALRM, NULL, &act);
        act.sa_flags &= ~SA_RESTART;
@@ -1143,10 +1162,6 @@ int main(int argc, char **argv)
        signal(SIGQUIT, SIG_IGN);
        signal(SIGINT, SIG_IGN);
 
-       setlocale(LC_ALL, "");
-       bindtextdomain(PACKAGE, LOCALEDIR);
-       textdomain(PACKAGE);
-
        setpriority(PRIO_PROCESS, 0, 0);
        initproctitle(argc, argv);
 
@@ -1156,7 +1171,7 @@ int main(int argc, char **argv)
         * -h is used by other servers to pass the name of the remote
         *    host to login so that it may be placed in utmp and wtmp
         */
-       while ((c = getopt(argc, argv, "fHh:pV")) != -1)
+       while ((c = getopt_long(argc, argv, "fHh:pV", longopts, NULL)) != -1)
                switch (c) {
                case 'f':
                        cxt.noauth = 1;
@@ -1180,21 +1195,22 @@ int main(int argc, char **argv)
                        break;
 
                case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
-               case '?':
+                       print_version(EXIT_SUCCESS);
+               case HELP_OPTION:
+                       usage();
                default:
-                       fprintf(stderr, _("Usage: login [-p] [-h <host>] [-H] [[-f] <username>]\n"));
-                       fputs(USAGE_SEPARATOR, stderr);
-                       fputs(_("Begin a session on the system.\n"), stderr);
-                       exit(EXIT_FAILURE);
+                       errtryhelp(EXIT_FAILURE);
                }
        argc -= optind;
        argv += optind;
 
        if (*argv) {
                char *p = *argv;
-               cxt.username = xstrdup(p);
+
+               /* username from command line */
+               cxt.cmd_username = xstrdup(p);
+               /* used temporary, it'll be replaced by username from PAM or/and cxt->pwd */
+               cxt.username = cxt.cmd_username;
 
                /* Wipe the name - some people mistype their password here. */
                /* (Of course we are too late, but perhaps this helps a little...) */
@@ -1226,10 +1242,11 @@ int main(int argc, char **argv)
         */
        loginpam_acct(&cxt);
 
-       if (!(cxt.pwd = get_passwd_entry(cxt.username, &pwdbuf, &_pwd))) {
+       cxt.pwd = xgetpwnam(cxt.username, &cxt.pwdbuf);
+       if (!cxt.pwd) {
                warnx(_("\nSession setup problem, abort."));
-               syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."),
-                      cxt.username, __FUNCTION__, __LINE__);
+               syslog(LOG_ERR, _("Invalid user name \"%s\". Abort."),
+                      cxt.username);
                pam_end(cxt.pamh, PAM_SYSTEM_ERR);
                sleepexit(EXIT_FAILURE);
        }
@@ -1338,10 +1355,7 @@ int main(int argc, char **argv)
 
        /* if the shell field has a space: treat it like a shell script */
        if (strchr(pwd->pw_shell, ' ')) {
-               buff = xmalloc(strlen(pwd->pw_shell) + 6);
-
-               strcpy(buff, "exec ");
-               strcat(buff, pwd->pw_shell);
+               xasprintf(&buff, "exec %s", pwd->pw_shell);
                childArgv[childArgc++] = "/bin/sh";
                childArgv[childArgc++] = "-sh";
                childArgv[childArgc++] = "-c";