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.   Derived from checkpassword-pam by Alexey Mahotkin <alexm@hsys.msk.ru> 2002-2004
  13.   Modified and enhanced by Manuel Mausz 2017
  14. */
  15.  
  16. #define _DEFAULT_SOURCE 1
  17.  
  18. #include <errno.h>
  19. #include <getopt.h>
  20. #include <grp.h>
  21. #include <pwd.h>
  22. #include <stdio.h>
  23. #include <stdint.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <unistd.h>
  27. #include <fcntl.h>
  28.  
  29. #include "logging.h"
  30. #include "dovecot-auth.h"
  31.  
  32. #define PACKAGE "checkpassword-dovecot"
  33. #define VERSION "0.2"
  34.  
  35. /* command line options */
  36. static int opt_dont_set_env = 0;
  37. static int opt_dont_chdir = 0;
  38. int opt_debug = 0;
  39. int opt_use_syslog = 0;
  40.  
  41. #define AUTH_LOGIN_URI "unix:///run/dovecot/auth-client"
  42. static const char *opt_auth_login_uri = AUTH_LOGIN_URI;
  43.  
  44. static const char* short_options = "a:dehHs:t:V";
  45.  
  46. enum { OPT_SYSLOG = 1 };
  47. static struct option long_options[] = {
  48. { "auth", required_argument, NULL, 'a' },
  49. { "debug", no_argument, NULL, 'd' },
  50. { "noenv", no_argument, NULL, 'e' },
  51. { "help", no_argument, NULL, 'h' },
  52. { "no-chdir", no_argument, NULL, 'H' },
  53. { "service", required_argument, NULL, 's' },
  54. { "syslog", no_argument, NULL, OPT_SYSLOG },
  55. { "timeout", required_argument, NULL, 't' },
  56. { "version", no_argument, NULL, 'V' },
  57. { NULL, 0, NULL, 0 }
  58. };
  59.  
  60. static const char *usage =
  61. "Usage: " PACKAGE " [OPTION]... -- prog...\n"
  62. "\n"
  63. "Authenticate using Dovecot and the checkpassword protocol:\n"
  64. " https://wiki2.dovecot.org/Design/AuthProtocol\n"
  65. " http://cr.yp.to/checkpwd/interface.html\n"
  66. "and optional run the program specified as 'prog'\n"
  67. "\n"
  68. "Options are:\n"
  69. " -a, --auth=URI\tURI to dovecot auth login socket\n"
  70. "\t\t\te.g. unix:///path/to/socket or tcp://ip:port\n"
  71. "\t\t\tdefault is " AUTH_LOGIN_URI "\n"
  72. " -d, --debug\t\tturn on debugging output\n"
  73. " -e, --noenv\t\tdo not set uid, gid, environment variables,\n"
  74. "\t\t\tand home directory\n"
  75. " -H, --no-chdir\tdo not change to home directory\n"
  76. " -h, --help\t\tdisplay this help and exit\n"
  77. " -s, --service=SERVICE\tspecify service name to use\n"
  78. " --syslog\t\tlog to syslog instead of stderr\n"
  79. " -t, --timeout=SECS\tconnection timeout in seconds\n"
  80. " -V, --version\t\tdisplay version information and exit\n";
  81.  
  82. /* checkpassword exit code */
  83. #define PROTOCOL_EXIT_SUCCESS 0
  84. #define PROTOCOL_EXIT_INVALID 1 /* invalid credentials */
  85. #define PROTOCOL_EXIT_FAILURE 2 /* wrong arguments */
  86. #define PROTOCOL_EXIT_TEMPFAIL 111
  87.  
  88. /* checkpassword protocol support */
  89. #define PROTOCOL_FD 3
  90. #define PROTOCOL_BACK_FD 4
  91. #define PROTOCOL_LEN 512
  92. static char upbuf[PROTOCOL_LEN];
  93.  
  94. /* pointers into upbuf[] */
  95. static char *username = NULL;
  96. static char *password = NULL;
  97.  
  98. #define V4MAPPREFIX "::ffff:"
  99. static char *tcpserver_ipaddress(const char *name)
  100. {
  101. char *remote_ip = getenv(name);
  102. if (!remote_ip)
  103. return NULL;
  104.  
  105. /* check for ipv4 mapped ipv6 address */
  106. const char *tmp = getenv("PROTO");
  107. if (tmp && strcmp(tmp, "TCP6") == 0) {
  108. if (!strncmp(remote_ip, V4MAPPREFIX, strlen(V4MAPPREFIX)))
  109. remote_ip += strlen(V4MAPPREFIX);
  110. }
  111. return remote_ip;
  112. }
  113.  
  114. int main(int argc, char *argv[])
  115. {
  116. char *service_name = NULL, *response = NULL;
  117. int exit_status = PROTOCOL_EXIT_INVALID, auth_timeout = 10;
  118.  
  119. log_init(argv[0]);
  120.  
  121. /* process command line options */
  122. opterr = 0;
  123. while (1) {
  124. int option_index = 0;
  125. int c = getopt_long(argc, argv, short_options, long_options,
  126. &option_index);
  127.  
  128. if (c == -1)
  129. break;
  130.  
  131. switch (c) {
  132. case OPT_SYSLOG:
  133. opt_use_syslog = 1;
  134. break;
  135.  
  136. case 'a':
  137. opt_auth_login_uri = strdup(optarg);
  138. break;
  139.  
  140. case 'd':
  141. opt_debug = 1;
  142. break;
  143.  
  144. case 'e':
  145. opt_dont_set_env = 1;
  146. break;
  147.  
  148. case 'h':
  149. puts(usage);
  150. exit(EXIT_SUCCESS);
  151.  
  152. case 'H':
  153. opt_dont_chdir = 1;
  154. break;
  155.  
  156. case 's':
  157. service_name = strdup(optarg);
  158. break;
  159.  
  160. case 't':
  161. auth_timeout = atoi(optarg);
  162. break;
  163.  
  164. case 'V':
  165. puts(PACKAGE " " VERSION);
  166. exit(EXIT_SUCCESS);
  167.  
  168. case '?':
  169. log_error("Invalid command line, see --help");
  170. exit(PROTOCOL_EXIT_FAILURE);
  171. }
  172. }
  173.  
  174. if (service_name == NULL) {
  175. log_error("Missing service name. Use --service=SERVICE");
  176. exit_status = PROTOCOL_EXIT_FAILURE;
  177. goto out;
  178. }
  179.  
  180. log_close();
  181. log_init(service_name);
  182.  
  183. /* read the username/password */
  184. FILE *protocol = fdopen(PROTOCOL_FD, "r");
  185. if (protocol == NULL) {
  186. log_error("Error opening fd %d: %s", PROTOCOL_FD, strerror(errno));
  187. exit_status = PROTOCOL_EXIT_FAILURE;
  188. goto out;
  189. }
  190. log_debug("Reading username and password");
  191. size_t uplen = fread(upbuf, 1, PROTOCOL_LEN, protocol);
  192. (void)fclose(protocol);
  193. if (uplen == 0) {
  194. log_error("Checkpassword protocol failure: zero bytes read");
  195. exit_status = PROTOCOL_EXIT_FAILURE;
  196. goto out;
  197. }
  198.  
  199. /* extract username */
  200. size_t i = 0;
  201. username = upbuf + i;
  202. while (upbuf[i++]) {
  203. if (i >= uplen) {
  204. log_error("Checkpassword protocol failure: username not provided");
  205. exit_status = PROTOCOL_EXIT_FAILURE;
  206. goto out;
  207. }
  208. }
  209. log_debug("Username '%s'", username);
  210.  
  211. /* extract password */
  212. password = upbuf + i;
  213. while (upbuf[i++]) {
  214. if (i >= uplen) {
  215. log_error("Checkpassword protocol failure: password not provided");
  216. exit_status = PROTOCOL_EXIT_FAILURE;
  217. goto out;
  218. }
  219. }
  220. log_debug("Password read successfully");
  221.  
  222. FILE *auth = auth_connect(opt_auth_login_uri, auth_timeout);
  223. if (auth == NULL) {
  224. exit_status = PROTOCOL_EXIT_FAILURE;
  225. goto out;
  226. }
  227.  
  228. char *tmp, *buf = auth_begin();
  229. buf = auth_add_parameter(buf, "service", service_name);
  230. if ((tmp = tcpserver_ipaddress("TCPLOCALIP")) != NULL) {
  231. buf = auth_add_parameter(buf, "lip", tmp);
  232. if ((tmp = tcpserver_ipaddress("TCPREMOTEIP")) != NULL)
  233. buf = auth_add_parameter(buf, "rip", tmp);
  234. if ((tmp = getenv("TCPLOCALPORT")) != NULL)
  235. buf = auth_add_parameter(buf, "lport", tmp);
  236. if ((tmp = getenv("TCPREMOTEPORT")) != NULL)
  237. buf = auth_add_parameter(buf, "rport", tmp);
  238. if (getenv("SMTPSECURED"))
  239. buf = auth_add_parameter(buf, "secured", NULL);
  240. }
  241.  
  242. int res = auth_login(auth, buf, username, password, &response);
  243. (void)fclose(auth);
  244. if (res != AUTH_OK) {
  245. if (res != AUTH_ERROR)
  246. log_debug("Login failed. Invalid credentials. Optional reason: %s",
  247. response);
  248. exit_status = PROTOCOL_EXIT_FAILURE;
  249. goto out;
  250. }
  251. exit_status = PROTOCOL_EXIT_SUCCESS;
  252. log_debug("Login successful");
  253.  
  254. if (strcmp(username, response) != 0) {
  255. log_debug("Username got changed to '%s'", response);
  256. username = response;
  257. }
  258.  
  259. if (fcntl(PROTOCOL_BACK_FD, F_GETFD) != -1 || errno != EBADF) {
  260. (void)write(PROTOCOL_BACK_FD, "USER=", 5);
  261. (void)write(PROTOCOL_BACK_FD, response, strlen(response));
  262. (void)write(PROTOCOL_BACK_FD, "\0", 1);
  263. if (close(PROTOCOL_BACK_FD) == -1) {
  264. log_error("close() error: %s", strerror(errno));
  265. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  266. goto out;
  267. }
  268. }
  269.  
  270. if (opt_dont_set_env)
  271. goto execute_program; /* skip setting up process environment */
  272.  
  273. /* switch to proper uid/gid/groups */
  274. struct passwd *pw = getpwnam(username);
  275. if (!pw) {
  276. if (opt_debug)
  277. log_error("Error getting information about %s from /etc/passwd: %s", username, strerror(errno));
  278. exit_status = PROTOCOL_EXIT_FAILURE;
  279. goto out;
  280. }
  281.  
  282. /* set supplementary groups */
  283. if (initgroups(username, pw->pw_gid) == -1) {
  284. log_error("Error setting supplementary groups for user %s: %s", username, strerror(errno));
  285. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  286. goto out;
  287. }
  288.  
  289. /* set gid */
  290. if (setgid(pw->pw_gid) == -1) {
  291. log_error("setgid(%d) error: %s", pw->pw_gid, strerror(errno));
  292. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  293. goto out;
  294. }
  295.  
  296. /* set uid */
  297. if (setuid(pw->pw_uid) == -1) {
  298. log_error("setuid(%d) error: %s", pw->pw_uid, strerror(errno));
  299. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  300. goto out;
  301. }
  302.  
  303. if (!opt_dont_chdir) {
  304. /* switch to user home directory */
  305. if (chdir(pw->pw_dir) == -1) {
  306. log_error("Error changing directory %s: %s", pw->pw_dir, strerror(errno));
  307. exit_status = PROTOCOL_EXIT_INVALID;
  308. goto out;
  309. }
  310. }
  311.  
  312. /* set $USER */
  313. if (setenv("USER", username, 1) == -1) {
  314. log_error("Error setting $USER to %s: %s", username, strerror(errno));
  315. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  316. goto out;
  317. }
  318.  
  319. /* set $HOME */
  320. if (setenv("HOME", pw->pw_dir, 1) == -1) {
  321. log_error("Error setting $HOME to %s: %s", pw->pw_dir, strerror(errno));
  322. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  323. goto out;
  324. }
  325.  
  326. /* set $SHELL */
  327. if (setenv("SHELL", pw->pw_shell, 1) == -1) {
  328. log_error("Error setting $SHELL to %s: %s", pw->pw_shell, strerror(errno));
  329. exit_status = PROTOCOL_EXIT_TEMPFAIL;
  330. goto out;
  331. }
  332.  
  333. if (response) {
  334. free(response);
  335. response = NULL;
  336. }
  337.  
  338. execute_program:
  339. /* execute the program, if any */
  340. if (optind < argc) {
  341. log_debug("Executing %s", argv[optind]);
  342. log_close();
  343.  
  344. execvp(argv[optind], argv + optind);
  345.  
  346. log_init(service_name);
  347. log_error("Cannot exec(%s): %s\n", argv[optind], strerror(errno));
  348. exit_status = PROTOCOL_EXIT_FAILURE;
  349. goto out;
  350. }
  351.  
  352. /* if no program was provided in command line, simply exit */
  353.  
  354. out:
  355. if (response)
  356. free(response);
  357.  
  358. log_debug("Exiting with status %d", exit_status);
  359. log_close();
  360. exit(exit_status);
  361. }
  362.