Download | Plain Text | No Line Numbers


  1. /*
  2.   This program is free software; you can redistribute it and/or
  3.   modify it under the terms of the GNU General Public License as
  4.   published by the Free Software Foundation; either version 2, or (at
  5.   your option) any later version.
  6.  
  7.   This program is distributed in the hope that it will be useful, but
  8.   WITHOUT ANY WARRANTY; without even the implied warranty of
  9.   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10.   General Public License for more details.
  11.  
  12.   Copyright (c) Manuel Mausz <manuel@mausz.at> 2017
  13.  
  14.   Dovecot auth support for checkpassword-dovecot
  15. */
  16.  
  17. #define _DEFAULT_SOURCE 1
  18. #define _GNU_SOURCE 1
  19.  
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <stdint.h>
  23. #include <stdbool.h>
  24. #include <string.h>
  25. #include <unistd.h>
  26. #include <limits.h>
  27. #include <inttypes.h>
  28. #include <arpa/inet.h>
  29. #include <sys/types.h>
  30. #include <sys/socket.h>
  31. #include <sys/select.h>
  32. #include <sys/un.h>
  33. #include <netdb.h>
  34. #include <errno.h>
  35. #include <fcntl.h>
  36.  
  37. #include "logging.h"
  38. #include "dovecot-auth.h"
  39.  
  40. #define AUTH_HANDSHAKE "VERSION\t1\t1\n"
  41.  
  42. static char line[8192];
  43. static int request_id = 0;
  44.  
  45. char *base64_encode(const char *src, size_t len, char *out);
  46.  
  47.  
  48. static int net_connect_wait(int sockfd, int timeout)
  49. {
  50. fd_set fdset;
  51. FD_ZERO(&fdset);
  52. FD_SET(sockfd, &fdset);
  53. struct timeval tv = { .tv_sec = timeout, .tv_usec = 0 };
  54.  
  55. if (select(sockfd + 1, NULL, &fdset, NULL, &tv) != 1) {
  56. if (errno == EINPROGRESS)
  57. errno = ETIMEDOUT;
  58. return 0;
  59. }
  60.  
  61. int so_error = 0;
  62. socklen_t len = sizeof(so_error);
  63. if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) != 0)
  64. return 0;
  65. if (so_error != 0) {
  66. errno = so_error;
  67. return 0;
  68. }
  69.  
  70. return 1;
  71. }
  72.  
  73. static int net_connect_inet(const char *host, const char *port, int timeout)
  74. {
  75. struct addrinfo hints = {
  76. .ai_family = AF_UNSPEC,
  77. .ai_socktype = SOCK_STREAM
  78. };
  79. struct addrinfo *result;
  80.  
  81. int s = getaddrinfo(host, port, &hints, &result);
  82. if (s != 0) {
  83. log_error("Auth: getaddrinfo: %s", gai_strerror(s));
  84. return -1;
  85. }
  86.  
  87. int sockfd = -1;
  88. for (struct addrinfo *r = result; r != NULL; r=r->ai_next) {
  89. sockfd = socket(r->ai_family,
  90. r->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, r->ai_protocol);
  91. if (sockfd == -1)
  92. continue;
  93. if (connect(sockfd, r->ai_addr, r->ai_addrlen) == 0
  94. || (errno == EINPROGRESS && net_connect_wait(sockfd, timeout))) {
  95. long opts = fcntl(sockfd, F_GETFL, NULL);
  96. opts &= ~O_NONBLOCK;
  97. (void)fcntl(sockfd, F_SETFL, opts);
  98. break;
  99. }
  100. close(sockfd);
  101. sockfd = -1;
  102. break; /* we only try the first record */
  103. }
  104.  
  105. freeaddrinfo(result);
  106. return sockfd;
  107. }
  108.  
  109. static int net_connect_unix(const char *path, int timeout)
  110. {
  111. int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
  112. if (sockfd < 0)
  113. return -1;
  114.  
  115. struct sockaddr_un saddr;
  116. saddr.sun_family = AF_UNIX;
  117. strcpy(saddr.sun_path, path);
  118. if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) == 0
  119. || (errno == EINPROGRESS && net_connect_wait(sockfd, timeout))) {
  120. long opts = fcntl(sockfd, F_GETFL, NULL);
  121. opts &= ~O_NONBLOCK;
  122. (void)fcntl(sockfd, F_SETFL, opts);
  123. return sockfd;
  124. }
  125.  
  126. close(sockfd);
  127. return -1;
  128. }
  129.  
  130. static int net_connect(const char *uri, int timeout)
  131. {
  132. int sockfd = -1;
  133. if (strncmp(uri, "tcp://", strlen("tcp://")) == 0) {
  134. char *path = strdup(uri + strlen("tcp://"));
  135.  
  136. char *portptr = strrchr(path, ':');
  137. if (portptr == NULL) {
  138. log_error("Auth: Invalid uri: port missing?");
  139. free(path);
  140. return -1;
  141. }
  142. *portptr = '\0';
  143.  
  144. char *host = path;
  145. if (host[0] == '[' && *(portptr - 1) == ']') {
  146. host++;
  147. *(portptr - 1) = '\0';
  148. }
  149.  
  150. sockfd = net_connect_inet(host, portptr + 1, timeout);
  151. free(path);
  152. }
  153. else if (strncmp(uri, "unix://", strlen("unix://")) == 0)
  154. sockfd = net_connect_unix(uri + strlen("unix://"), timeout);
  155. else {
  156. log_error("Auth: Invalid uri: unknown syntax");
  157. return -1;
  158. }
  159.  
  160. if (sockfd < 0)
  161. log_error("Auth: Unable to connect to socket: %s", strerror(errno));
  162. return sockfd;
  163. }
  164.  
  165. static int auth_handshake(FILE *sock)
  166. {
  167. log_debug("Auth: sending handshake");
  168. if (fwrite(AUTH_HANDSHAKE, 1, strlen(AUTH_HANDSHAKE), sock)
  169. != strlen(AUTH_HANDSHAKE)
  170. || fprintf(sock, "CPID\t%u\n", getppid()) < 0) {
  171. log_error("Auth: Error while sending handshake: %s", strerror(errno));
  172. return 0;
  173. }
  174.  
  175. if (fgets(line, sizeof(line), sock) == NULL) {
  176. log_error("Auth: No handshake received from server");
  177. return 0;
  178. }
  179.  
  180. if (strncmp(line, "VERSION\t", 8) != 0) {
  181. log_error("Auth: Invalid handshake, no version received");
  182. return 0;
  183. }
  184.  
  185. bool has_plaintext = false;
  186. while(fgets(line, sizeof(line), sock) != NULL) {
  187. if (strncmp(line, "MECH\tPLAIN\t", 11) == 0)
  188. has_plaintext = true;
  189. else if (strcmp(line, "DONE\n") == 0)
  190. break;
  191. }
  192.  
  193. if (!has_plaintext) {
  194. log_error("Auth: Plaintext auth not supported");
  195. return 0;
  196. }
  197.  
  198. return 1;
  199. }
  200.  
  201. FILE *auth_connect(const char *uri, int timeout)
  202. {
  203. log_debug("Auth: connecting to %s", uri);
  204. int sockfd = net_connect(uri, timeout);
  205. if (sockfd < 0)
  206. return NULL;
  207. log_debug("Auth: connected");
  208.  
  209. FILE *sock = fdopen(sockfd, "r+");
  210. if (!sock) {
  211. log_error("Auth: Error during fdopen: %s", strerror(errno));
  212. close(sockfd);
  213. return NULL;
  214. }
  215.  
  216. if (!auth_handshake(sock)) {
  217. fclose(sock);
  218. return NULL;
  219. }
  220.  
  221. return sock;
  222. }
  223.  
  224. char *auth_begin()
  225. {
  226. char *buf = line;
  227. buf[0] = '\0';
  228. buf += sprintf(buf, "AUTH\t%d\tPLAIN", ++request_id);
  229. return buf;
  230. }
  231.  
  232. char *auth_add_parameter(char *buf, const char *name, const char *value)
  233. {
  234. size_t name_len = strlen(name) + 1;
  235. size_t value_len = (value) ? strlen(value) + 1 : 0;
  236. if (buf == NULL || strlen(buf) + name_len + value_len >= sizeof(line) - 1)
  237. return NULL;
  238.  
  239. *buf++ = '\t';
  240. buf = mempcpy(buf, name, name_len - 1);
  241. if (value) {
  242. *buf++ = '=';
  243. buf = mempcpy(buf, value, value_len - 1);
  244. }
  245. *buf = '\0';
  246. return buf;
  247. }
  248.  
  249. int auth_login(FILE *sock, char *buf, const char *username,
  250. const char *password, char **response)
  251. {
  252. size_t username_len = strlen(username);
  253. size_t password_len = strlen(password);
  254. size_t credentials_len = username_len * 2 + password_len + 2;
  255. size_t b64_len = (credentials_len * 4 / 3 + 3) & ~3;
  256. buf = auth_add_parameter(buf, "resp=", NULL);
  257. if (buf == NULL || strlen(buf) + b64_len + 1 >= sizeof(line) - 1) {
  258. log_error("Auth: Credentials are too long to fit");
  259. return AUTH_ERROR;
  260. }
  261.  
  262. // format: master_user \0 username \0 password
  263. char *credentials, *tmp = credentials = malloc(credentials_len);
  264. tmp = mempcpy(tmp, username, username_len);
  265. *tmp++ = '\0';
  266. tmp = mempcpy(tmp, username, username_len);
  267. *tmp++ = '\0';
  268. mempcpy(tmp, password, password_len);
  269.  
  270. buf = base64_encode(credentials, credentials_len, buf);
  271. memcpy(buf, "\n\0", 2);
  272.  
  273. log_debug("Auth: sending credentials");
  274. if (fwrite(line, 1, strlen(line), sock) != strlen(line)) {
  275. log_error("Auth: Error while sending credentials: %s", strerror(errno));
  276. return AUTH_ERROR;
  277. }
  278.  
  279. if (fgets(line, sizeof(line), sock) == NULL) {
  280. log_error("Auth: No auth reply received");
  281. return AUTH_ERROR;
  282. }
  283.  
  284. int ret = AUTH_FAIL;
  285. if (strncmp(line, "OK\t", 3) == 0)
  286. ret = AUTH_OK;
  287. else if (strncmp(line, "FAIL\t", 5) != 0)
  288. log_error("Auth: received unknown auth reply: %s", line);
  289.  
  290. if (response) {
  291. char *token, *saveptr;
  292. for(token = strtok_r(line, "\t\n", &saveptr); token;
  293. token = strtok_r(NULL, "\t\n", &saveptr)) {
  294. if (ret == 0 && strncmp(token, "user=", 5) == 0)
  295. *response = strdup(token + 5);
  296. else if (strncmp(token, "reason=", 7) == 0)
  297. *response = strdup(token + 7);
  298. }
  299. }
  300.  
  301. #if 0
  302. /* if login socket is of type "login" we need to cancel the request as we're
  303.   * not requesting more information from the master
  304.   * otherwise dovecot will log a warning upon disconnect
  305.   */
  306. if (ret == AUTH_OK && fprintf(sock, "CANCEL\t%d\n", request_id) < 0) {
  307. log_error("Auth: Error while canceling request: %s", strerror(errno));
  308. return 0;
  309. }
  310. #endif
  311.  
  312. return ret;
  313. }
  314.  
  315. char *base64_encode(const char *src, size_t len, char *out)
  316. {
  317. static const uint8_t base64_table[65] =
  318. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  319. uint8_t *pos;
  320. const uint8_t *end, *in;
  321.  
  322. end = (uint8_t *)src + len;
  323. in = (uint8_t *)src;
  324. pos = (uint8_t *)out;
  325. while (end - in >= 3) {
  326. *pos++ = base64_table[ in[0] >> 2 ];
  327. *pos++ = base64_table[ ((in[0] & 0x03) << 4) | (in[1] >> 4) ];
  328. *pos++ = base64_table[ ((in[1] & 0x0f) << 2) | (in[2] >> 6) ];
  329. *pos++ = base64_table[ in[2] & 0x3f ];
  330. in += 3;
  331. }
  332.  
  333. if (end - in) {
  334. *pos++ = base64_table[ in[0] >> 2 ];
  335. if (end - in == 1) {
  336. *pos++ = base64_table[ (in[0] & 0x03) << 4 ];
  337. *pos++ = '=';
  338. } else {
  339. *pos++ = base64_table[ ((in[0] & 0x03) << 4) | (in[1] >> 4) ];
  340. *pos++ = base64_table[ (in[1] & 0x0f) << 2 ];
  341. }
  342. *pos++ = '=';
  343. }
  344.  
  345. *pos = '\0';
  346. return (char *)pos;
  347. }
  348.