]> granicus.if.org Git - sudo/commitdiff
Rewrote all the old sudo 1.1/1.2 code. Timestamp handling is now
authorTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 22 Jul 1999 12:19:11 +0000 (12:19 +0000)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 22 Jul 1999 12:19:11 +0000 (12:19 +0000)
done more reasonably--better sanity checks and tty-based stamps are
now done as files in a directory with the same name as the invoking
user, eg. /var/run/sudo/millert/ttyp1.  It is not currently possible
to mix tty and non-tty based ticket schemes but this may change in
the future (it requires sudo to use a directory instead of a file
in the non-tty case).  Also, ``sudo -k'' now sets the ticket back
to the epoch and ``sudo -K'' really deletes the file.  That way you
don't get the lecture again just because you killed your ticket in
.logout.  BSD-style copyright now.

check.c

diff --git a/check.c b/check.c
index 47dfbc80797fb8f4341ec008e8d30b6ab18cada8..e2d9e4f293a43ea44ab9c8be9e3a784c38b25d01 100644 (file)
--- a/check.c
+++ b/check.c
@@ -1,32 +1,28 @@
 /*
- *  CU sudo version 1.6 -- allows users to execute commands as root and others
- *  Copyright (c) 1991  The Root Group, Inc.
- *  Copyright (c) 1994,1996,1998,1999 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 1994,1996,1998,1999 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
  *
- *  Please send bugs, changes, problems to sudo-bugs@courtesan.com
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
  *
- *  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; either version 1, or (at your option)
- *  any later version.
- *
- *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- *******************************************************************
- *
- *  check.c
- *
- *  check_user() only returns if the user's timestamp file
- *  is current or if they enter a correct password.
- *
- *  Jeff Nieusma  Thu Mar 21 22:39:07 MST 1991
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "config.h"
@@ -44,6 +40,7 @@
 #ifdef HAVE_STRINGS_H
 #include <strings.h>
 #endif /* HAVE_STRINGS_H */
+#include <errno.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <time.h>
 static const char rcsid[] = "$Sudo$";
 #endif /* lint */
 
-/*
- * Prototypes for local functions
- */
-static int   check_timestamp           __P((void));
-static int   touch                     __P((char *));
-static void  update_timestamp          __P((void));
-static void  reminder                  __P((void));
+       int   user_is_exempt            __P((void));
+static char *build_timestampfile       __P((void));
+static int   timestamp_status          __P((char *, char *, int));
+static int   touch                     __P((char *, time_t));
+#ifndef NO_PASSWD
 static char *expand_prompt             __P((char *, char *, char *));
-int   user_is_exempt                   __P((void));
+static void  lecture                   __P((void));
+static void  update_timestamp          __P((char *));
 
+/* Status codes for timestamp_status() */
+#define TS_CURRENT     0
+#define TS_OLD         1
+#define TS_MISSING     2
+#define TS_ERROR       3
 
 /*
- * Globals
+ * This function only returns if the user can successfully
+ * verify who s/he is.  
  */
-static int   timedir_is_good;
-static char  timestampfile[MAXPATHLEN];
-
-
-/********************************************************************
- *
- *  check_user()
- *
- *  This function only returns if the user can successfully
- *  verify who s/he is.  
- */
-
 void
 check_user()
 {
-    int rtn;
-#ifdef POSIX_SIGNALS
-    sigset_t set, oset;
-#else
-    int omask;
-#endif /* POSIX_SIGNALS */
+    char *timestampfile;
+    int status;
 
-    if (user_is_exempt())      /* some users don't need to enter a passwd */
+    if (user_uid == 0 || user_is_exempt())
        return;
 
-    /*
-     * Block SIGINT and SIGTSTP during authentication so the user
-     * can't abort the logging.
-     */
-#ifdef POSIX_SIGNALS
-    (void) sigemptyset(&set);
-    (void) sigaddset(&set, SIGINT);
-    (void) sigaddset(&set, SIGTSTP);
-    (void) sigprocmask(SIG_BLOCK, &set, &oset);
-#else
-    omask = sigblock(sigmask(SIGINT)|sigmask(SIGTSTP));
-#endif /* POSIX_SIGNALS */
+    timestampfile = build_timestampfile();
+    status = timestamp_status(timestampfile, user_name, TRUE);
+    if (status != TS_CURRENT) {
+       if (status == TS_MISSING)
+           lecture();          /* first time through they get a lecture */
+
+       /* Expand any escapes in the prompt. */
+       user_prompt = expand_prompt(user_prompt, user_name, user_shost);
+
+       verify_user();
+    }
+    if (status != TS_ERROR)
+       update_timestamp(timestampfile);
+}
 
-    rtn = check_timestamp();
-    if (rtn && user_uid) {     /* if timestamp is not current... */
+/*
+ * Standard sudo lecture.
+ * TODO: allow the user to specify a file name instead at compile time.
+ */
+static void
+lecture()
+{
 #ifndef NO_MESSAGE
-       if (rtn == 2)
-           reminder();         /* do the reminder if ticket file is new */
+    (void) fputs("\n\
+We trust you have received the usual lecture from the local System\n\
+Administrator. It usually boils down to these two things:\n\
+\n\
+       #1) Respect the privacy of others.\n\
+       #2) Think before you type.\n\n",
+       stderr);
 #endif /* NO_MESSAGE */
+}
+
+/*
+ * Update the time on the timestamp file or create it if neccesary.
+ */
+static void
+update_timestamp(timestampfile)
+    char *timestampfile;
+{
 
-       /* expand any escapes in the prompt */
-       prompt = expand_prompt(prompt, user_name, shost);
+    set_perms(PERM_ROOT, 0);           /* become root */
 
-#ifdef HAVE_SIA
-       sia_attempt_auth();
-#elif HAVE_PAM
-       pam_attempt_auth();
-#else  /* !HAVE_SIA && !HAVE_PAM */
-       check_passwd();
-#endif /* HAVE_SIA */
+    if (touch(timestampfile, time(NULL)) < 0) {
+       int fd = open(timestampfile, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+
+       if (fd < 0)
+           log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
+       else
+           close(fd);
     }
 
-    /* Unblock signals */
-#ifdef POSIX_SIGNALS
-    (void) sigprocmask(SIG_SETMASK, &oset, NULL);
-#else
-    (void) sigsetmask(omask);       
-#endif /* POSIX_SIGNALS */
+    set_perms(PERM_USER, 0);           /* relinquish root */
+}
 
-    update_timestamp();
+/*
+ * Expand %h and %u escapes in the prompt and pass back the dynamically
+ * allocated result.  Returns the same string if there are no escapes.
+ */
+static char *
+expand_prompt(old_prompt, user, host)
+    char *old_prompt;
+    char *user;
+    char *host;
+{
+    size_t len;
+    int subst;
+    char *p, *np, *new_prompt, lastchar;
+
+    /* How much space do we need to malloc for the prompt? */
+    subst = 0;
+    for (p = old_prompt, len = strlen(old_prompt), lastchar = '\0'; *p; p++) {
+       if (lastchar == '%') {
+           if (*p == 'h') {
+               len += strlen(user_shost) - 2;
+               subst = 1;
+           } else if (*p == 'u') {
+               len += strlen(user_name) - 2;
+               subst = 1;
+           }
+       }
+
+       if (lastchar == '%' && *p == '%') {
+           lastchar = '\0';
+           len--;
+       } else
+           lastchar = *p;
+    }
+
+    if (subst) {
+       new_prompt = (char *) emalloc(len + 1);
+       for (p = user_prompt, np = new_prompt; *p; p++) {
+           if (lastchar == '%' && (*p == 'h' || *p == 'u' || *p == '%')) {
+               /* substiture user/host name */
+               if (*p == 'h') {
+                   np--;
+                   strcpy(np, user_shost);
+                   np += strlen(user_shost);
+               } else if (*p == 'u') {
+                   np--;
+                   strcpy(np, user_name);
+                   np += strlen(user_name);
+               }
+           } else
+               *np++ = *p;
+
+           if (lastchar == '%' && *p == '%')
+               lastchar = '\0';
+           else
+               lastchar = *p;
+       }
+       *np = '\0';
+    } else
+       new_prompt = user_prompt;
+
+    return(new_prompt);
 }
 
+#else /* NO_PASSWD */
 
-/********************************************************************
- *
- *  user_is_exempt()
- *
- *  this function checks the user is exempt from supplying a password.
+/*
+ * Stub function, just returns.
  */
+void
+check_user()
+{
+    return;
+}
+#endif /* NO_PASSWD */
 
+/*
+ * Checks if the user is exempt from supplying a password.
+ */
 int
 user_is_exempt()
 {
@@ -181,322 +250,236 @@ user_is_exempt()
 #endif
 }
 
-
-/********************************************************************
- *
- *  check_timestamp()
- *
- *  this function checks the timestamp file.  If it is within
- *  TIMEOUT minutes, no password will be required
- */
-
-static int
-check_timestamp()
-{
-    register char *p;
-    struct stat statbuf;
-    register int timestamp_is_old = -1;
-    time_t now;
-
-#ifdef USE_TTY_TICKETS
-    if (p = strrchr(tty, '/'))
-       p++;
-    else
-       p = tty;
-
-    if (sizeof(_PATH_SUDO_TIMEDIR) + strlen(user_name) + strlen(p) + 2 >
-       sizeof(timestampfile)) {
-       (void) fprintf(stderr, "%s: path too long: %s/%s:%s\n", Argv[0],
-                      _PATH_SUDO_TIMEDIR, user_name, p);
-       exit(1);                                              
-    }
-    (void) sprintf(timestampfile, "%s/%s:%s", _PATH_SUDO_TIMEDIR, user_name, p);
-#else
-    if (sizeof(_PATH_SUDO_TIMEDIR) + strlen(user_name) + 1 >
-       sizeof(timestampfile)) {
-       (void) fprintf(stderr, "%s: path too long: %s/%s\n", Argv[0],
-                      _PATH_SUDO_TIMEDIR, user_name);
-       exit(1);                                              
-    }
-    (void) sprintf(timestampfile, "%s/%s", _PATH_SUDO_TIMEDIR, user_name);
-#endif /* USE_TTY_TICKETS */
-
-    timedir_is_good = 1;       /* now there's an assumption for ya... */
-
-    /* become root */
-    set_perms(PERM_ROOT, 0);
-
-    /*
-     * walk through the path one directory at a time
-     */
-    for (p = timestampfile + 1; (p = strchr(p, '/')); *p++ = '/') {
-       *p = '\0';
-       if (stat(timestampfile, &statbuf) < 0) {
-           if (strcmp(timestampfile, _PATH_SUDO_TIMEDIR))
-               (void) fprintf(stderr, "Cannot stat() %s\n", timestampfile);
-           timedir_is_good = 0;
-           *p = '/';
-           break;
-       }
-    }
-
-    /*
-     * if all the directories are stat()able
-     */
-    if (timedir_is_good) {
-       /*
-        * last component in _PATH_SUDO_TIMEDIR must be owned by root
-        * and mode 0700 or we ignore the timestamps in it.
-        */
-       if (statbuf.st_uid != 0 || (statbuf.st_mode & 0000077)) {
-           timedir_is_good = 0;
-           timestamp_is_old = 2;
-           log_error(BAD_STAMPDIR);
-           inform_user(BAD_STAMPDIR);
-       } else if (stat(timestampfile, &statbuf)) {
-           /* timestamp file does not exist? */
-           timestamp_is_old = 2;       /* return (2)          */
-       } else {
-           /* check the time against the timestamp file */
-           now = time((time_t *) NULL);
-           if (TIMEOUT && now - statbuf.st_mtime < 60 * TIMEOUT) {
-               /* check for bogus time on the stampfile */
-               if (statbuf.st_mtime > now + 60 * TIMEOUT * 2) {
-                   timestamp_is_old = 2;       /* bogus time value */
-                   log_error(BAD_STAMPFILE);
-                   inform_user(BAD_STAMPFILE);
-                   remove_timestamp();
-               } else {
-                   timestamp_is_old = 0;       /* time value is reasonable */
-               }
-           } else {
-               timestamp_is_old = 1;   /* else make 'em enter password */
-           }
-       }
-    }
-    /*
-     * there was a problem stat()ing a directory
-     */
-    else {
-       timestamp_is_old = 2;   /* user has to enter password + reminder */
-       /* make the TIMEDIR directory */
-       if (mkdir(_PATH_SUDO_TIMEDIR, S_IRWXU)) {
-           perror("check_timestamp: mkdir");
-           timedir_is_good = 0;
-       } else {
-           timedir_is_good = 1;        /* _PATH_SUDO_TIMEDIR now exists */
-       }
-    }
-
-    /* relinquish root */
-    set_perms(PERM_USER, 0);
-
-    return (timestamp_is_old);
-}
-
-
-/********************************************************************
- *
- *  touch()
- *
- *  This function updates the access and modify times on a file
- *  via utime(2).
+/*
+ * Update the access and modify times on a file.
  */
-
 static int
-touch(file)
+touch(file, when)
     char *file;
+    time_t when;
 {
-#if defined(HAVE_UTIME) && !defined(HAVE_UTIME_NULL)
 #ifdef HAVE_UTIME_POSIX
-#define UTP            (&ut)
-    struct utimbuf ut;
+    struct utimbuf ut, *utp;
 
-    ut.actime = ut.modtime = time(NULL);
+    ut.actime = ut.modtime = when;
+    utp = &ut;
 #else
-#define UTP            (ut)
     /* old BSD <= 4.3 has no struct utimbuf */
-    time_t ut[2];
+    time_t utp[2];
 
-    ut[0] = ut[1] = time(NULL);
+    utp[0] = utp[1] = when;
 #endif /* HAVE_UTIME_POSIX */
-#else
-#define UTP            NULL
-#endif /* HAVE_UTIME && !HAVE_UTIME_NULL */
-
-    return(utime(file, UTP));
-}
-#undef UTP
 
-
-/********************************************************************
- *
- *  update_timestamp()
- *
- *  This function changes the timestamp to "now"
- */
-
-static void
-update_timestamp()
-{
-    if (timedir_is_good) {
-       /* become root */
-       set_perms(PERM_ROOT, 0);
-
-       if (touch(timestampfile) < 0) {
-           int fd = open(timestampfile, O_WRONLY | O_CREAT | O_TRUNC, 0600);
-
-           if (fd < 0)
-               perror("update_timestamp: open");
-           else
-               close(fd);
-       }
-
-       /* relinquish root */
-       set_perms(PERM_USER, 0);
-    }
+    return(utime(file, utp));
 }
 
-
-/********************************************************************
- *
- *  remove_timestamp()
- *
- *  This function removes the timestamp ticket file
+/*
+ * Returns a pointer to static storage containing the timestamp path.
  */
-
-void
-remove_timestamp()
+static char *
+build_timestampfile()
 {
 #ifdef USE_TTY_TICKETS
     char *p;
+#endif
+    static char timestampfile[MAXPATHLEN];
+
+    if (timestampfile[0] != '\0')
+       return(timestampfile);
 
+#ifdef USE_TTY_TICKETS
     if (p = strrchr(tty, '/'))
        p++;
     else
        p = tty;
-
     if (sizeof(_PATH_SUDO_TIMEDIR) + strlen(user_name) + strlen(p) + 2 >
-       sizeof(timestampfile)) {
-       (void) fprintf(stderr, "%s: path too long: %s/%s:%s\n", Argv[0],
-                      _PATH_SUDO_TIMEDIR, user_name, p);
-       exit(1);                                              
-    }
-    (void) sprintf(timestampfile, "%s/%s:%s", _PATH_SUDO_TIMEDIR, user_name, p);
+       MAXPATHLEN)
+       log_error(0, "timestamp path too long: %s/%s/%s", _PATH_SUDO_TIMEDIR,
+           user_name, p);
+    (void) sprintf(timestampfile, "%s/%s/%s", _PATH_SUDO_TIMEDIR, user_name, p);
 #else
-    if (sizeof(_PATH_SUDO_TIMEDIR) + strlen(user_name) + 1 >
-       sizeof(timestampfile)) {
-       (void) fprintf(stderr, "%s: path too long: %s/%s\n", Argv[0],
-                      _PATH_SUDO_TIMEDIR, user_name);
-       exit(1);                                              
-    }
+    if (sizeof(_PATH_SUDO_TIMEDIR) + strlen(user_name) + 1 > MAXPATHLEN)
+       log_error(0, "timestamp path too long: %s/%s", _PATH_SUDO_TIMEDIR,
+           user_name);
     (void) sprintf(timestampfile, "%s/%s", _PATH_SUDO_TIMEDIR, user_name);
 #endif /* USE_TTY_TICKETS */
 
-    /* become root */
-    set_perms(PERM_ROOT, 0);
-
-    /* remove the ticket file */
-    (void) unlink(timestampfile);
-
-    /* relinquish root */
-    set_perms(PERM_USER, 0);
+    return(timestampfile);
 }
 
-
-#ifndef NO_MESSAGE
-/********************************************************************
- *
- *  reminder()
- *
- *  this function just prints the the reminder message
+/*
+ * Check the timestamp file and directory and return their status.
  */
-
-static void
-reminder()
+static int
+timestamp_status(timestampfile, user, make_dirs)
+    char *timestampfile;
+    char *user;
+    int make_dirs;
 {
-#ifdef SHORT_MESSAGE
-    (void) fprintf(stderr, "\n%s\n%s\n\n%s\n%s\n\n",
-#else
-    (void) fprintf(stderr, "\n%s%s%s\n%s\n%s\n%s\n\n%s\n%s\n\n%s\n%s\n\n",
-       "    CU Sudo version ", version,
-       ", Copyright (c) 1991 The Root Group, Inc.",
-       "    Copyright (c) 1994, 1996, 1998, 1999 Todd C. Miller.",
-       "    sudo comes with ABSOLUTELY NO WARRANTY.  This is free software,",
-       "    and you are welcome to redistribute it under certain conditions.",
+#ifdef USE_TTY_TICKETS
+    char *p;
 #endif
-       "We trust you have received the usual lecture from the local System",
-       "Administrator. It usually boils down to these two things:",
-       "        #1) Respect the privacy of others.",
-       "        #2) Think before you type."
-    );
-}
-#endif /* NO_MESSAGE */
+    struct stat sb;
+    time_t now;
+    int status = TS_ERROR;
 
+    set_perms(PERM_ROOT, 0);           /* become root */
 
-/********************************************************************
- *
- *  expand_prompt()
- *
- *  expand %h and %u in the prompt and pass back the dynamically
- *  allocated result.  Returns the same string if no escapes.
- */
+    /*
+     * Sanity check _PATH_SUDO_TIMEDIR and make it if it doesn't already exist.
+     * We start out assuming the worst (that the dir is not sane) and
+     * if it is ok upgrade the status to ``no timestamp file''.
+     * Note that we don't check the parent(s) of _PATH_SUDO_TIMEDIR for
+     * sanity since the sudo dir is often just located in /tmp.
+     */
+    if (lstat(_PATH_SUDO_TIMEDIR, &sb) == 0) {
+       if (!S_ISDIR(sb.st_mode))
+           log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
+               _PATH_SUDO_TIMEDIR, sb.st_mode);
+       else if (sb.st_uid != 0)
+           log_error(NO_EXIT, "%s owned by uid %ld, should be owned by root",
+               _PATH_SUDO_TIMEDIR, (long) sb.st_uid);
+       else if ((sb.st_mode & 0000022))
+           log_error(NO_EXIT,
+               "%s writable by non-owner (0%o), should be mode 0700",
+               _PATH_SUDO_TIMEDIR, sb.st_mode);
+       else {
+           if ((sb.st_mode & 0000777) != 0700)
+               (void) chmod(_PATH_SUDO_TIMEDIR, 0700);
+           status = TS_MISSING;
+       }
+    } else if (errno != ENOENT) {
+       log_error(NO_EXIT|USE_ERRNO, "can't stat %s", _PATH_SUDO_TIMEDIR);
+    } else {
+       /* No _PATH_SUDO_TIMEDIR, try to make one. */
+       if (make_dirs) {
+           if (mkdir(_PATH_SUDO_TIMEDIR, S_IRWXU))
+               log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
+                   _PATH_SUDO_TIMEDIR);
+           else
+               status = TS_MISSING;
+       }
+    }
+    if (status == TS_ERROR)
+       return(TS_ERROR);
 
-static char *
-expand_prompt(old_prompt, user, host)
-    char *old_prompt;
-    char *user;
-    char *host;
-{
-    size_t len;
-    int subst;
-    char *p, *np, *new_prompt, lastchar;
+#ifdef USE_TTY_TICKETS
+    /*
+     * Sanity check the user's ticket dir.  We start by downgrading
+     * the status to TS_ERROR.  If the ticket dir exists and is sane
+     * this will be upgraded to TS_OLD.  If the dir does not exist and
+     * we can make it successfully, it will be upgraded to TS_MISSING.
+     */
+    status = TS_ERROR;                 /* downgrade status again */
+    p = strrchr(timestampfile, '/');
+    *p = '\0';
+    if (lstat(timestampfile, &sb) == 0) {
+       if (!S_ISDIR(sb.st_mode)) {
+           if (S_ISREG(sb.st_mode))
+               (void) unlink(timestampfile);   /* convert from old style */
+           else
+               log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
+                   timestampfile, sb.st_mode);
+       } else if (sb.st_uid != 0)
+           log_error(NO_EXIT, "%s owned by uid %ld, should be owned by root",
+               timestampfile, (long) sb.st_uid);
+       else if ((sb.st_mode & 0000022))
+           log_error(NO_EXIT,
+               "%s writable by non-owner (0%o), should be mode 0700",
+               timestampfile, sb.st_mode);
+       else {
+           if ((sb.st_mode & 0000777) != 0700)
+               (void) chmod(timestampfile, 0700);
+           status = TS_OLD;
+       }
+    } else if (errno != ENOENT) {
+       log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
+    } else {
+       /* No user ticket dir, try to make one. */
+       if (make_dirs) {
+           if (mkdir(timestampfile, S_IRWXU))
+               log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampfile);
+           else
+               status = TS_MISSING;
+       }
+    }
+    *p = '/';
+#endif /* USE_TTY_TICKETS */
 
-    /* How much space do we need to malloc for the prompt? */
-    subst = 0;
-    for (p = old_prompt, len = strlen(old_prompt), lastchar = '\0'; *p; p++) {
-       if (lastchar == '%') {
-           if (*p == 'h') {
-               len += strlen(shost) - 2;
-               subst = 1;
-           } else if (*p == 'u') {
-               len += strlen(user_name) - 2;
-               subst = 1;
+    /*
+     * Sanity check the timestamp file, if it exists.
+     * Status has been upgraded to TS_MISSING.
+     * XXX - should deal with case where TTY tickets were in use but no longer are
+     *       that means that %s/user being as dir is ok.
+     */
+    if (lstat(timestampfile, &sb) == 0) {
+       if (!S_ISREG(sb.st_mode))
+           log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
+               timestampfile, sb.st_mode);
+       else {
+           /* If bad uid or file mode, complain and kill the bogus file. */
+           if (sb.st_uid != 0) {
+               log_error(NO_EXIT,
+                   "%s owned by uid %ld, should be owned by root",
+                   timestampfile, (long) sb.st_uid);
+               (void) unlink(timestampfile);
+           } else if ((sb.st_mode & 0000022)) {
+               log_error(NO_EXIT,
+                   "%s writable by non-owner (0%o), should be mode 0600",
+                   timestampfile, sb.st_mode);
+               (void) unlink(timestampfile);
+           } else {
+               /* If not mode 0600, fix it. */
+               if ((sb.st_mode & 0000777) != 0600)
+                   (void) chmod(timestampfile, 0600);
+
+               /* Check the time against the timestamp file */
+               now = time((time_t *) NULL);
+               if (TIMEOUT && now - sb.st_mtime < 60 * TIMEOUT) {
+                   /*
+                    * Check for bogus time on the stampfile.  The clock may
+                    * have been reset or someone could be trying to fake us.
+                    */
+                   if (sb.st_mtime > now + 60 * TIMEOUT * 2) {
+                       log_error(NO_EXIT,
+                           "timestamp too far in the future: %20.20s",
+                           4 + ctime(&sb.st_mtime));
+                       (void) unlink(timestampfile);
+                   } else
+                       status = TS_CURRENT;
+               } else
+                   status = TS_OLD;
            }
        }
-
-       if (lastchar == '%' && *p == '%') {
-           lastchar = '\0';
-           len--;
-       } else
-           lastchar = *p;
+    } else if (errno != ENOENT) {
+       log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
+       status = TS_ERROR;
     }
 
-    if (subst) {
-       new_prompt = (char *) emalloc(len + 1);
-       for (p = prompt, np = new_prompt; *p; p++) {
-           if (lastchar == '%' && (*p == 'h' || *p == 'u' || *p == '%')) {
-               /* substiture user/host name */
-               if (*p == 'h') {
-                   np--;
-                   strcpy(np, shost);
-                   np += strlen(shost);
-               } else if (*p == 'u') {
-                   np--;
-                   strcpy(np, user_name);
-                   np += strlen(user_name);
-               }
-           } else
-               *np++ = *p;
-
-           if (lastchar == '%' && *p == '%')
-               lastchar = '\0';
-           else
-               lastchar = *p;
-       }
-       *np = '\0';
-    } else
-       new_prompt = prompt;
+    set_perms(PERM_USER, 0);           /* relinquish root */
+    return(status);
+}
 
-    return(new_prompt);
+/*
+ * Removes the timestamp ticket file.
+ */
+void
+remove_timestamp(remove)
+    int remove;
+{
+    char *timestampfile;
+    int status;
+
+    timestampfile = build_timestampfile();
+    status = timestamp_status(timestampfile, user_name, FALSE);
+    if (status != TS_ERROR && status != TS_MISSING) {
+       set_perms(PERM_ROOT, 0);                /* become root */
+       if (remove)
+           (void) unlink(timestampfile);
+       else
+           if (touch(timestampfile, 0))
+               (void) fprintf(stderr, "%s: can't reset %s to epoch\n",
+                   Argv[0], timestampfile);
+       set_perms(PERM_USER, 0);                /* relinquish root */
+    }
 }