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.
-
- Copyright (c) Manuel Mausz <manuel@mausz.at> 2017
-
- Dovecot auth support for checkpassword-dovecot
- */
-
- #define _DEFAULT_SOURCE 1
- #define _GNU_SOURCE 1
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdint.h>
- #include <stdbool.h>
- #include <string.h>
- #include <unistd.h>
- #include <limits.h>
- #include <inttypes.h>
- #include <arpa/inet.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/select.h>
- #include <sys/un.h>
- #include <netdb.h>
- #include <errno.h>
- #include <fcntl.h>
-
- #include "logging.h"
- #include "dovecot-auth.h"
-
- #define AUTH_HANDSHAKE "VERSION\t1\t1\n"
-
- static char line[8192];
- static int request_id = 0;
-
- char *base64_encode(const char *src, size_t len, char *out);
-
-
- static int net_connect_wait(int sockfd, int timeout)
- {
- fd_set fdset;
- FD_ZERO(&fdset);
- FD_SET(sockfd, &fdset);
- struct timeval tv = { .tv_sec = timeout, .tv_usec = 0 };
-
- if (select(sockfd + 1, NULL, &fdset, NULL, &tv) != 1) {
- if (errno == EINPROGRESS)
- errno = ETIMEDOUT;
- return 0;
- }
-
- int so_error = 0;
- socklen_t len = sizeof(so_error);
- if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) != 0)
- return 0;
- if (so_error != 0) {
- errno = so_error;
- return 0;
- }
-
- return 1;
- }
-
- static int net_connect_inet(const char *host, const char *port, int timeout)
- {
- struct addrinfo hints = {
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_STREAM
- };
- struct addrinfo *result;
-
- int s = getaddrinfo(host, port, &hints, &result);
- if (s != 0) {
- log_error("Auth: getaddrinfo: %s", gai_strerror(s));
- return -1;
- }
-
- int sockfd = -1;
- for (struct addrinfo *r = result; r != NULL; r=r->ai_next) {
- sockfd = socket(r->ai_family,
- r->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, r->ai_protocol);
- if (sockfd == -1)
- continue;
- if (connect(sockfd, r->ai_addr, r->ai_addrlen) == 0
- || (errno == EINPROGRESS && net_connect_wait(sockfd, timeout))) {
- long opts = fcntl(sockfd, F_GETFL, NULL);
- opts &= ~O_NONBLOCK;
- (void)fcntl(sockfd, F_SETFL, opts);
- break;
- }
- close(sockfd);
- sockfd = -1;
- break; /* we only try the first record */
- }
-
- freeaddrinfo(result);
- return sockfd;
- }
-
- static int net_connect_unix(const char *path, int timeout)
- {
- int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
- if (sockfd < 0)
- return -1;
-
- struct sockaddr_un saddr;
- saddr.sun_family = AF_UNIX;
- if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) == 0
- || (errno == EINPROGRESS && net_connect_wait(sockfd, timeout))) {
- long opts = fcntl(sockfd, F_GETFL, NULL);
- opts &= ~O_NONBLOCK;
- (void)fcntl(sockfd, F_SETFL, opts);
- return sockfd;
- }
-
- close(sockfd);
- return -1;
- }
-
- static int net_connect(const char *uri, int timeout)
- {
- int sockfd = -1;
-
- if (portptr == NULL) {
- log_error("Auth: Invalid uri: port missing?");
- return -1;
- }
- *portptr = '\0';
-
- char *host = path;
- if (host[0] == '[' && *(portptr - 1) == ']') {
- host++;
- *(portptr - 1) = '\0';
- }
-
- sockfd = net_connect_inet(host, portptr + 1, timeout);
- }
- else {
- log_error("Auth: Invalid uri: unknown syntax");
- return -1;
- }
-
- if (sockfd < 0)
- return sockfd;
- }
-
- static int auth_handshake(FILE *sock)
- {
- log_debug("Auth: sending handshake");
- return 0;
- }
-
- log_error("Auth: No handshake received from server");
- return 0;
- }
-
- log_error("Auth: Invalid handshake, no version received");
- return 0;
- }
-
- bool has_plaintext = false;
- has_plaintext = true;
- break;
- }
-
- if (!has_plaintext) {
- log_error("Auth: Plaintext auth not supported");
- return 0;
- }
-
- return 1;
- }
-
- FILE *auth_connect(const char *uri, int timeout)
- {
- log_debug("Auth: connecting to %s", uri);
- int sockfd = net_connect(uri, timeout);
- if (sockfd < 0)
- return NULL;
- log_debug("Auth: connected");
-
- if (!sock) {
- close(sockfd);
- return NULL;
- }
-
- if (!auth_handshake(sock)) {
- return NULL;
- }
-
- return sock;
- }
-
- char *auth_begin()
- {
- char *buf = line;
- buf[0] = '\0';
- return buf;
- }
-
- char *auth_add_parameter(char *buf, const char *name, const char *value)
- {
- return NULL;
-
- *buf++ = '\t';
- buf = mempcpy(buf, name, name_len - 1);
- if (value) {
- *buf++ = '=';
- buf = mempcpy(buf, value, value_len - 1);
- }
- *buf = '\0';
- return buf;
- }
-
- int auth_login(FILE *sock, char *buf, const char *username,
- const char *password, char **response)
- {
- size_t credentials_len = username_len * 2 + password_len + 2;
- size_t b64_len = (credentials_len * 4 / 3 + 3) & ~3;
- buf = auth_add_parameter(buf, "resp=", NULL);
- log_error("Auth: Credentials are too long to fit");
- return AUTH_ERROR;
- }
-
- // format: master_user \0 username \0 password
- tmp = mempcpy(tmp, username, username_len);
- *tmp++ = '\0';
- tmp = mempcpy(tmp, username, username_len);
- *tmp++ = '\0';
- mempcpy(tmp, password, password_len);
-
- buf = base64_encode(credentials, credentials_len, buf);
-
- log_debug("Auth: sending credentials");
- return AUTH_ERROR;
- }
-
- log_error("Auth: No auth reply received");
- return AUTH_ERROR;
- }
-
- int ret = AUTH_FAIL;
- ret = AUTH_OK;
- log_error("Auth: received unknown auth reply: %s", line);
-
- if (response) {
- char *token, *saveptr;
- for(token = strtok_r(line, "\t\n", &saveptr); token;
- token = strtok_r(NULL, "\t\n", &saveptr)) {
- *response = strdup(token + 5);
- *response = strdup(token + 7);
- }
- }
-
- #if 0
- /* if login socket is of type "login" we need to cancel the request as we're
- * not requesting more information from the master
- * otherwise dovecot will log a warning upon disconnect
- */
- return 0;
- }
- #endif
-
- return ret;
- }
-
- char *base64_encode(const char *src, size_t len, char *out)
- {
- static const uint8_t base64_table[65] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- uint8_t *pos;
- const uint8_t *end, *in;
-
- end = (uint8_t *)src + len;
- in = (uint8_t *)src;
- pos = (uint8_t *)out;
- while (end - in >= 3) {
- *pos++ = base64_table[ in[0] >> 2 ];
- *pos++ = base64_table[ ((in[0] & 0x03) << 4) | (in[1] >> 4) ];
- *pos++ = base64_table[ ((in[1] & 0x0f) << 2) | (in[2] >> 6) ];
- *pos++ = base64_table[ in[2] & 0x3f ];
- in += 3;
- }
-
- if (end - in) {
- *pos++ = base64_table[ in[0] >> 2 ];
- if (end - in == 1) {
- *pos++ = base64_table[ (in[0] & 0x03) << 4 ];
- *pos++ = '=';
- } else {
- *pos++ = base64_table[ ((in[0] & 0x03) << 4) | (in[1] >> 4) ];
- *pos++ = base64_table[ (in[1] & 0x0f) << 2 ];
- }
- *pos++ = '=';
- }
-
- *pos = '\0';
- return (char *)pos;
- }
-