diff options
Diffstat (limited to 'kcheckpass/kcheckpass.c')
-rw-r--r-- | kcheckpass/kcheckpass.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/kcheckpass/kcheckpass.c b/kcheckpass/kcheckpass.c new file mode 100644 index 000000000..6a0550969 --- /dev/null +++ b/kcheckpass/kcheckpass.c @@ -0,0 +1,448 @@ +/***************************************************************** + * + * kcheckpass - Simple password checker + * + * 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 2 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + * kcheckpass is a simple password checker. Just invoke and + * send it the password on stdin. + * + * If the password was accepted, the program exits with 0; + * if it was rejected, it exits with 1. Any other exit + * code signals an error. + * + * It's hopefully simple enough to allow it to be setuid + * root. + * + * Compile with -DHAVE_VSYSLOG if you have vsyslog(). + * Compile with -DHAVE_PAM if you have a PAM system, + * and link with -lpam -ldl. + * Compile with -DHAVE_SHADOW if you have a shadow + * password system. + * + * Copyright (C) 1998, Caldera, Inc. + * Released under the GNU General Public License + * + * Olaf Kirch <okir@caldera.de> General Framework and PAM support + * Christian Esken <esken@kde.org> Shadow and /etc/passwd support + * Roberto Teixeira <maragato@kde.org> other user (-U) support + * Oswald Buddenhagen <ossi@kde.org> Binary server mode + * + * Other parts were taken from kscreensaver's passwd.cpp. + * + *****************************************************************/ + +#include "kcheckpass.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <syslog.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> + +/* Compatibility: accept some options from environment variables */ +#define ACCEPT_ENV + +#define THROTTLE 3 + +static int havetty, sfd = -1, nullpass; + +static char * +conv_legacy (ConvRequest what, const char *prompt) +{ + char *p, *p2; + int len; + char buf[1024]; + + switch (what) { + case ConvGetBinary: + break; + case ConvGetNormal: + /* there is no prompt == 0 case */ + if (!havetty) + break; + /* i guess we should use /dev/tty ... */ + fputs(prompt, stdout); + fflush(stdout); + if (!fgets(buf, sizeof(buf), stdin)) + return 0; + len = strlen(buf); + if (len && buf[len - 1] == '\n') + buf[--len] = 0; + return strdup(buf); + case ConvGetHidden: + if (havetty) { +#ifdef HAVE_GETPASSPHRASE + p = getpassphrase(prompt ? prompt : "Password: "); +#else + p = getpass(prompt ? prompt : "Password: "); +#endif + p2 = strdup(p); + memset(p, 0, strlen(p)); + return p2; + } else { + if (prompt) + break; + if ((len = read(0, buf, sizeof(buf) - 1)) < 0) { + message("Cannot read password\n"); + return 0; + } else { + if (len && buf[len - 1] == '\n') + --len; + buf[len] = 0; + p2 = strdup(buf); + memset(buf, 0, len); + return p2; + } + } + case ConvPutInfo: + message("Information: %s\n", prompt); + return 0; + case ConvPutError: + message("Error: %s\n", prompt); + return 0; + } + message("Authentication backend requested data type which cannot be handled.\n"); + return 0; +} + + +static int +Reader (void *buf, int count) +{ + int ret, rlen; + + for (rlen = 0; rlen < count; ) { + dord: + ret = read (sfd, (void *)((char *)buf + rlen), count - rlen); + if (ret < 0) { + if (errno == EINTR) + goto dord; + if (errno == EAGAIN) + break; + return -1; + } + if (!ret) + break; + rlen += ret; + } + return rlen; +} + +static void +GRead (void *buf, int count) +{ + if (Reader (buf, count) != count) { + message ("Communication breakdown on read\n"); + exit(15); + } +} + +static void +GWrite (const void *buf, int count) +{ + if (write (sfd, buf, count) != count) { + message ("Communication breakdown on write\n"); + exit(15); + } +} + +static void +GSendInt (int val) +{ + GWrite (&val, sizeof(val)); +} + +static void +GSendStr (const char *buf) +{ + unsigned len = buf ? strlen (buf) + 1 : 0; + GWrite (&len, sizeof(len)); + GWrite (buf, len); +} + +static void +GSendArr (int len, const char *buf) +{ + GWrite (&len, sizeof(len)); + GWrite (buf, len); +} + +static int +GRecvInt (void) +{ + int val; + + GRead (&val, sizeof(val)); + return val; +} + +static char * +GRecvStr (void) +{ + unsigned len; + char *buf; + + if (!(len = GRecvInt())) + return (char *)0; + if (len > 0x1000 || !(buf = malloc (len))) { + message ("No memory for read buffer\n"); + exit(15); + } + GRead (buf, len); + buf[len - 1] = 0; /* we're setuid ... don't trust "them" */ + return buf; +} + +static char * +GRecvArr (void) +{ + unsigned len; + char *arr; + + if (!(len = (unsigned) GRecvInt())) + return (char *)0; + if (len > 0x10000 || !(arr = malloc (len))) { + message ("No memory for read buffer\n"); + exit(15); + } + GRead (arr, len); + return arr; +} + + +static char * +conv_server (ConvRequest what, const char *prompt) +{ + GSendInt (what); + switch (what) { + case ConvGetBinary: + { + unsigned const char *up = (unsigned const char *)prompt; + int len = up[3] | (up[2] << 8) | (up[1] << 16) | (up[0] << 24); + GSendArr (len, prompt); + return GRecvArr (); + } + case ConvGetNormal: + case ConvGetHidden: + { + char *msg; + GSendStr (prompt); + msg = GRecvStr (); + if (msg && (GRecvInt() & IsPassword) && !*msg) + nullpass = 1; + return msg; + } + case ConvPutInfo: + case ConvPutError: + default: + GSendStr (prompt); + return 0; + } +} + +void +message(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +#ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 +#endif + +static void ATTR_NORETURN +usage(int exitval) +{ + message( + "usage: kcheckpass {-h|[-c caller] [-m method] [-U username|-S handle]}\n" + " options:\n" + " -h this help message\n" + " -U username authenticate the specified user instead of current user\n" + " -S handle operate in binary server mode on file descriptor handle\n" + " -c caller the calling application, effectively the PAM service basename\n" + " -m method use the specified authentication method (default: \"classic\")\n" + " exit codes:\n" + " 0 success\n" + " 1 invalid password\n" + " 2 cannot read password database\n" + " Anything else tells you something's badly hosed.\n" + ); + exit(exitval); +} + +int +main(int argc, char **argv) +{ +#ifdef HAVE_PAM + const char *caller = KCHECKPASS_PAM_SERVICE; +#endif + const char *method = "classic"; + const char *username = 0; +#ifdef ACCEPT_ENV + char *p; +#endif + struct passwd *pw; + int c, nfd, lfd; + uid_t uid; + time_t nexttime; + AuthReturn ret; + struct flock lk; + char fname[64], fcont[64]; + +#ifdef HAVE_OSF_C2_PASSWD + initialize_osf_security(argc, argv); +#endif + + /* Make sure stdout/stderr are open */ + for (c = 1; c <= 2; c++) { + if (fcntl(c, F_GETFL) == -1) { + if ((nfd = open("/dev/null", O_WRONLY)) < 0) { + message("cannot open /dev/null: %s\n", strerror(errno)); + exit(10); + } + if (c != nfd) { + dup2(nfd, c); + close(nfd); + } + } + } + + havetty = isatty(0); + + while ((c = getopt(argc, argv, "hc:m:U:S:")) != -1) { + switch (c) { + case 'h': + usage(0); + break; + case 'c': +#ifdef HAVE_PAM + caller = optarg; +#endif + break; + case 'm': + method = optarg; + break; + case 'U': + username = optarg; + break; + case 'S': + sfd = atoi(optarg); + break; + default: + message("Command line option parsing error\n"); + usage(10); + } + } + +#ifdef ACCEPT_ENV +# ifdef HAVE_PAM + if ((p = getenv("KDE_PAM_ACTION"))) + caller = p; +# endif + if ((p = getenv("KCHECKPASS_USER"))) + username = p; +#endif + + uid = getuid(); + if (!username) { + if (!(p = getenv("LOGNAME")) || !(pw = getpwnam(p)) || pw->pw_uid != uid) + if (!(p = getenv("USER")) || !(pw = getpwnam(p)) || pw->pw_uid != uid) + if (!(pw = getpwuid(uid))) { + message("Cannot determinate current user\n"); + return AuthError; + } + if (!(username = strdup(pw->pw_name))) { + message("Out of memory\n"); + return AuthError; + } + } + + /* + * Throttle kcheckpass invocations to avoid abusing it for bruteforcing + * the password. This delay belongs to the *previous* invocation, where + * we can't enforce it reliably (without risking giving away the result + * before it is due). We don't differentiate between success and failure - + * it's not expected to have a noticable adverse effect. + */ + if ( uid != geteuid() ) { + sprintf(fname, "/var/run/kcheckpass.%d", uid); + if ((lfd = open(fname, O_RDWR | O_CREAT | O_NOFOLLOW, 0600)) < 0) { + message("Cannot open lockfile\n"); + return AuthError; + } + + lk.l_type = F_WRLCK; + lk.l_whence = SEEK_SET; + lk.l_start = lk.l_len = 0; + if (fcntl(lfd, F_SETLKW, &lk)) { + message("Cannot obtain lock\n"); + return AuthError; + } + + if ((c = read(lfd, fcont, sizeof(fcont)-1)) > 0 && + (fcont[c] = '\0', sscanf(fcont, "%ld", &nexttime) == 1)) + { + time_t ct = time(0); + if (nexttime > ct && nexttime < ct + THROTTLE) + sleep(nexttime - ct); + } + + lseek(lfd, 0, SEEK_SET); + write(lfd, fcont, sprintf(fcont, "%lu\n", time(0) + THROTTLE)); + + close(lfd); + } + + /* Now do the fandango */ + ret = Authenticate( +#ifdef HAVE_PAM + caller, +#endif + method, + username, + sfd < 0 ? conv_legacy : conv_server); + + if (ret == AuthBad) { + message("Authentication failure\n"); + if (!nullpass) { + openlog("kcheckpass", LOG_PID, LOG_AUTH); + syslog(LOG_NOTICE, "Authentication failure for %s (invoked by uid %d)", username, uid); + } + } + + return ret; +} + +void +dispose(char *str) +{ + memset(str, 0, strlen(str)); + free(str); +} + +/***************************************************************** + The real authentication methods are in separate source files. + Look in checkpass_*.c +*****************************************************************/ |