Download | Plain Text | No Line Numbers
- /*
- 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, 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.
-
- Derived from checkpassword-pam by Alexey Mahotkin <alexm@hsys.msk.ru> 2002-2004
- Modified and enhanced by Manuel Mausz 2017
- */
-
- #define _DEFAULT_SOURCE 1
-
- #include <errno.h>
- #include <getopt.h>
- #include <grp.h>
- #include <pwd.h>
- #include <stdio.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <fcntl.h>
-
- #include "logging.h"
- #include "dovecot-auth.h"
-
- #define PACKAGE "checkpassword-dovecot"
- #define VERSION "0.2"
-
- /* command line options */
- static int opt_dont_set_env = 0;
- static int opt_dont_chdir = 0;
- int opt_debug = 0;
- int opt_use_syslog = 0;
-
- #define AUTH_LOGIN_URI "unix:///run/dovecot/auth-client"
- static const char *opt_auth_login_uri = AUTH_LOGIN_URI;
-
- static const char* short_options = "a:dehHs:t:V";
-
- enum { OPT_SYSLOG = 1 };
- static struct option long_options[] = {
- { "auth", required_argument, NULL, 'a' },
- { "debug", no_argument, NULL, 'd' },
- { "noenv", no_argument, NULL, 'e' },
- { "help", no_argument, NULL, 'h' },
- { "no-chdir", no_argument, NULL, 'H' },
- { "service", required_argument, NULL, 's' },
- { "syslog", no_argument, NULL, OPT_SYSLOG },
- { "timeout", required_argument, NULL, 't' },
- { "version", no_argument, NULL, 'V' },
- { NULL, 0, NULL, 0 }
- };
-
- static const char *usage =
- "Usage: " PACKAGE " [OPTION]... -- prog...\n"
- "\n"
- "Authenticate using Dovecot and the checkpassword protocol:\n"
- " https://wiki2.dovecot.org/Design/AuthProtocol\n"
- " http://cr.yp.to/checkpwd/interface.html\n"
- "and optional run the program specified as 'prog'\n"
- "\n"
- "Options are:\n"
- " -a, --auth=URI\tURI to dovecot auth login socket\n"
- "\t\t\te.g. unix:///path/to/socket or tcp://ip:port\n"
- "\t\t\tdefault is " AUTH_LOGIN_URI "\n"
- " -d, --debug\t\tturn on debugging output\n"
- " -e, --noenv\t\tdo not set uid, gid, environment variables,\n"
- "\t\t\tand home directory\n"
- " -H, --no-chdir\tdo not change to home directory\n"
- " -h, --help\t\tdisplay this help and exit\n"
- " -s, --service=SERVICE\tspecify service name to use\n"
- " --syslog\t\tlog to syslog instead of stderr\n"
- " -t, --timeout=SECS\tconnection timeout in seconds\n"
- " -V, --version\t\tdisplay version information and exit\n";
-
- /* checkpassword exit code */
- #define PROTOCOL_EXIT_SUCCESS 0
- #define PROTOCOL_EXIT_INVALID 1 /* invalid credentials */
- #define PROTOCOL_EXIT_FAILURE 2 /* wrong arguments */
- #define PROTOCOL_EXIT_TEMPFAIL 111
-
- /* checkpassword protocol support */
- #define PROTOCOL_FD 3
- #define PROTOCOL_BACK_FD 4
- #define PROTOCOL_LEN 512
- static char upbuf[PROTOCOL_LEN];
-
- /* pointers into upbuf[] */
- static char *username = NULL;
- static char *password = NULL;
-
- #define V4MAPPREFIX "::ffff:"
- static char *tcpserver_ipaddress(const char *name)
- {
- if (!remote_ip)
- return NULL;
-
- /* check for ipv4 mapped ipv6 address */
- }
- return remote_ip;
- }
-
- int main(int argc, char *argv[])
- {
- char *service_name = NULL, *response = NULL;
- int exit_status = PROTOCOL_EXIT_INVALID, auth_timeout = 10;
-
- log_init(argv[0]);
-
- /* process command line options */
- opterr = 0;
- while (1) {
- int option_index = 0;
- int c = getopt_long(argc, argv, short_options, long_options,
- &option_index);
-
- if (c == -1)
- break;
-
- switch (c) {
- case OPT_SYSLOG:
- opt_use_syslog = 1;
- break;
-
- case 'a':
- opt_auth_login_uri = strdup(optarg);
- break;
-
- case 'd':
- opt_debug = 1;
- break;
-
- case 'e':
- opt_dont_set_env = 1;
- break;
-
- case 'h':
-
- case 'H':
- opt_dont_chdir = 1;
- break;
-
- case 's':
- service_name = strdup(optarg);
- break;
-
- case 't':
- break;
-
- case 'V':
-
- case '?':
- log_error("Invalid command line, see --help");
- }
- }
-
- if (service_name == NULL) {
- log_error("Missing service name. Use --service=SERVICE");
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
-
- log_close();
- log_init(service_name);
-
- /* read the username/password */
- if (protocol == NULL) {
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
- log_debug("Reading username and password");
- if (uplen == 0) {
- log_error("Checkpassword protocol failure: zero bytes read");
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
-
- /* extract username */
- size_t i = 0;
- username = upbuf + i;
- while (upbuf[i++]) {
- if (i >= uplen) {
- log_error("Checkpassword protocol failure: username not provided");
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
- }
- log_debug("Username '%s'", username);
-
- /* extract password */
- password = upbuf + i;
- while (upbuf[i++]) {
- if (i >= uplen) {
- log_error("Checkpassword protocol failure: password not provided");
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
- }
- log_debug("Password read successfully");
-
- FILE *auth = auth_connect(opt_auth_login_uri, auth_timeout);
- if (auth == NULL) {
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
-
- char *tmp, *buf = auth_begin();
- buf = auth_add_parameter(buf, "service", service_name);
- if ((tmp = tcpserver_ipaddress("TCPLOCALIP")) != NULL) {
- buf = auth_add_parameter(buf, "lip", tmp);
- if ((tmp = tcpserver_ipaddress("TCPREMOTEIP")) != NULL)
- buf = auth_add_parameter(buf, "rip", tmp);
- buf = auth_add_parameter(buf, "lport", tmp);
- buf = auth_add_parameter(buf, "rport", tmp);
- buf = auth_add_parameter(buf, "secured", NULL);
- }
-
- int res = auth_login(auth, buf, username, password, &response);
- if (res != AUTH_OK) {
- if (res != AUTH_ERROR)
- log_debug("Login failed. Invalid credentials. Optional reason: %s",
- response);
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
- exit_status = PROTOCOL_EXIT_SUCCESS;
- log_debug("Login successful");
-
- log_debug("Username got changed to '%s'", response);
- username = response;
- }
-
- if (fcntl(PROTOCOL_BACK_FD, F_GETFD) != -1 || errno != EBADF) {
- (void)write(PROTOCOL_BACK_FD, "USER=", 5);
- (void)write(PROTOCOL_BACK_FD, "\0", 1);
- if (close(PROTOCOL_BACK_FD) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
- }
-
- if (opt_dont_set_env)
- goto execute_program; /* skip setting up process environment */
-
- /* switch to proper uid/gid/groups */
- struct passwd *pw = getpwnam(username);
- if (!pw) {
- if (opt_debug)
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
-
- /* set supplementary groups */
- if (initgroups(username, pw->pw_gid) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
-
- /* set gid */
- if (setgid(pw->pw_gid) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
-
- /* set uid */
- if (setuid(pw->pw_uid) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
-
- if (!opt_dont_chdir) {
- /* switch to user home directory */
- if (chdir(pw->pw_dir) == -1) {
- exit_status = PROTOCOL_EXIT_INVALID;
- goto out;
- }
- }
-
- /* set $USER */
- if (setenv("USER", username, 1) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
-
- /* set $HOME */
- if (setenv("HOME", pw->pw_dir, 1) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
-
- /* set $SHELL */
- if (setenv("SHELL", pw->pw_shell, 1) == -1) {
- exit_status = PROTOCOL_EXIT_TEMPFAIL;
- goto out;
- }
-
- if (response) {
- response = NULL;
- }
-
- execute_program:
- /* execute the program, if any */
- if (optind < argc) {
- log_debug("Executing %s", argv[optind]);
- log_close();
-
- execvp(argv[optind], argv + optind);
-
- log_init(service_name);
- exit_status = PROTOCOL_EXIT_FAILURE;
- goto out;
- }
-
- /* if no program was provided in command line, simply exit */
-
- out:
- if (response)
-
- log_debug("Exiting with status %d", exit_status);
- log_close();
- }
-