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