PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/tin-2.0.1/src/auth.c

#
C | 486 lines | 295 code | 49 blank | 142 comment | 132 complexity | a0957e445441b4506bd172ba2e7a6463 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /*
  2. * Project : tin - a Usenet reader
  3. * Module : auth.c
  4. * Author : Dirk Nimmich <nimmich@muenster.de>
  5. * Created : 1997-04-05
  6. * Updated : 2009-06-27
  7. * Notes : Routines to authenticate to a news server via NNTP.
  8. * DON'T USE get_respcode() THROUGHOUT THIS CODE.
  9. *
  10. * Copyright (c) 1997-2011 Dirk Nimmich <nimmich@muenster.de>
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions
  15. * are met:
  16. * 1. Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. * 2. Redistributions in binary form must reproduce the above copyright
  19. * notice, this list of conditions and the following disclaimer in the
  20. * documentation and/or other materials provided with the distribution.
  21. * 3. The name of the author may not be used to endorse or promote
  22. * products derived from this software without specific prior written
  23. * permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
  26. * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  27. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  28. * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  29. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  30. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  31. * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  32. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  33. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  34. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  35. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. */
  37. #ifndef TIN_H
  38. # include "tin.h"
  39. #endif /* !TIN_H */
  40. #ifndef TCURSES_H
  41. # include "tcurses.h"
  42. #endif /* !TCURSES_H */
  43. /*
  44. * we don't need authentication stuff at all if we don't have NNTP support
  45. */
  46. #ifdef NNTP_ABLE
  47. /*
  48. * local prototypes
  49. */
  50. static int do_authinfo_user(char *server, char *authuser, char *authpass);
  51. static t_bool read_newsauth_file(char *server, char *authuser, char *authpass);
  52. static t_bool authinfo_plain(char *server, char *authuser, t_bool startup);
  53. # ifdef USE_SASL
  54. static char *sasl_auth_plain(char *user, char *pass);
  55. static int do_authinfo_sasl_plain(char *authuser, char *authpass);
  56. # endif /* USE_SASL */
  57. /*
  58. * Read the $HOME/.newsauth file and put authentication username
  59. * and password for the specified server in the given strings.
  60. * Returns TRUE if at least a password was found, FALSE if there was
  61. * no .newsauth file or no matching server.
  62. */
  63. static t_bool
  64. read_newsauth_file(
  65. char *server,
  66. char *authuser,
  67. char *authpass)
  68. {
  69. FILE *fp;
  70. char *_authpass;
  71. char *ptr;
  72. char filename[PATH_LEN];
  73. char line[PATH_LEN];
  74. int found = 0;
  75. int fd;
  76. struct stat statbuf;
  77. joinpath(filename, sizeof(filename), homedir, ".newsauth");
  78. if ((fp = fopen(filename, "r"))) {
  79. if ((fd = fileno(fp)) == -1) {
  80. fclose(fp);
  81. return FALSE;
  82. }
  83. if (fstat(fd, &statbuf) == -1) {
  84. fclose(fp);
  85. return FALSE;
  86. }
  87. # ifndef FILE_MODE_BROKEN
  88. if (S_ISREG(statbuf.st_mode) && (statbuf.st_mode|S_IRUSR|S_IWUSR) != (S_IRUSR|S_IWUSR|S_IFREG)) {
  89. error_message(4, _(txt_error_insecure_permissions), filename, statbuf.st_mode);
  90. /*
  91. * TODO: fix permssions?
  92. * fchmod(fd, S_IRUSR|S_IWUSR);
  93. */
  94. }
  95. # endif /* !FILE_MODE_BROKEN */
  96. /*
  97. * Search through authorization file for correct NNTP server
  98. * File has format: 'nntp-server' 'password' ['username']
  99. */
  100. while (fgets(line, sizeof(line), fp) != NULL) {
  101. /* strip trailing newline character */
  102. ptr = strchr(line, '\n');
  103. if (ptr != NULL)
  104. *ptr = '\0';
  105. /* Get server from 1st part of the line */
  106. ptr = strpbrk(line, " \t");
  107. if (ptr == NULL) /* no passwd, no auth, skip */
  108. continue;
  109. *ptr++ = '\0'; /* cut off server part */
  110. if ((strcasecmp(line, server)))
  111. continue; /* wrong server, keep on */
  112. /* Get password from 2nd part of the line */
  113. while (*ptr == ' ' || *ptr == '\t')
  114. ptr++; /* skip any blanks */
  115. _authpass = ptr;
  116. if (*_authpass == '"') { /* skip "embedded" password string */
  117. ptr = strrchr(_authpass, '"');
  118. if ((ptr != NULL) && (ptr > _authpass)) {
  119. _authpass++;
  120. *ptr++ = '\0'; /* cut off trailing " */
  121. } else /* no matching ", proceede as normal */
  122. ptr = _authpass;
  123. }
  124. /* Get user from 3rd part of the line */
  125. ptr = strpbrk(ptr, " \t"); /* find next separating blank */
  126. if (ptr != NULL) { /* a 3rd argument follows */
  127. while (*ptr == ' ' || *ptr == '\t') /* skip any blanks */
  128. *ptr++ = '\0';
  129. if (*ptr != '\0') /* if its not just empty */
  130. strcpy(authuser, ptr); /* so will replace default user */
  131. }
  132. strcpy(authpass, _authpass);
  133. found++;
  134. break; /* if we end up here, everything seems OK */
  135. }
  136. fclose(fp);
  137. return (found > 0);
  138. } else
  139. return FALSE;
  140. }
  141. /*
  142. * Perform authentication with AUTHINFO USER method. Return response
  143. * code from server.
  144. *
  145. * we don't handle ERR_ENCRYPT right now
  146. */
  147. static int
  148. do_authinfo_user(
  149. char *server,
  150. char *authuser,
  151. char *authpass)
  152. {
  153. char line[PATH_LEN];
  154. int ret;
  155. snprintf(line, sizeof(line), "AUTHINFO USER %s", authuser);
  156. # ifdef DEBUG
  157. if (debug & DEBUG_NNTP)
  158. debug_print_file("NNTP", "authorization %s", line);
  159. # endif /* DEBUG */
  160. put_server(line);
  161. if ((ret = get_only_respcode(NULL, 0)) != NEED_AUTHDATA)
  162. return ret;
  163. if ((authpass == NULL) || (*authpass == '\0')) {
  164. # ifdef DEBUG
  165. if (debug & DEBUG_NNTP)
  166. debug_print_file("NNTP", "authorization failed: no password");
  167. # endif /* DEBUG */
  168. error_message(2, _(txt_auth_failed_nopass), server);
  169. return ERR_AUTHBAD;
  170. }
  171. snprintf(line, sizeof(line), "AUTHINFO PASS %s", authpass);
  172. # ifdef DEBUG
  173. if (debug & DEBUG_NNTP)
  174. debug_print_file("NNTP", "authorization %s", line);
  175. # endif /* DEBUG */
  176. put_server(line);
  177. ret = get_only_respcode(line, sizeof(line));
  178. if (!batch_mode || verbose || ret != OK_AUTH)
  179. wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  180. return ret;
  181. }
  182. /*
  183. * NNTP user authorization. Returns TRUE if authorization succeeded,
  184. * FALSE if not.
  185. *
  186. * tries AUTHINFO SASL PLAIN (if available) fist and if not succcessfull
  187. * AUTHINFO USER/PASS
  188. *
  189. * If username/passwd already given, and server wasn't changed, retry those.
  190. * Otherwise, read password from ~/.newsauth or, if not present or no matching
  191. * server found, from console.
  192. *
  193. * The ~/.newsauth authorization file has the format:
  194. * nntpserver1 password [user]
  195. * nntpserver2 password [user]
  196. * etc.
  197. */
  198. static t_bool
  199. authinfo_plain(
  200. char *server,
  201. char *authuser,
  202. t_bool startup)
  203. {
  204. char *authpass;
  205. int ret = ERR_AUTHBAD, changed;
  206. static char authusername[PATH_LEN] = "";
  207. static char authpassword[PATH_LEN] = "";
  208. static char last_server[PATH_LEN] = "";
  209. static t_bool already_failed = FALSE;
  210. static t_bool initialized = FALSE;
  211. changed = strcmp(server, last_server); /* do we need new auth values? */
  212. strncpy(last_server, server, PATH_LEN - 1);
  213. last_server[PATH_LEN - 1] = '\0';
  214. /*
  215. * Let's try the previous auth pair first, if applicable.
  216. * Else, proceed to the other mechanisms.
  217. */
  218. if (initialized && !changed && !already_failed) {
  219. # ifdef USE_SASL
  220. if (nntp_caps.sasl & SASL_PLAIN)
  221. ret = do_authinfo_sasl_plain(authusername, authpassword);
  222. if (ret != OK_AUTH)
  223. # endif /* USE_SASL */
  224. {
  225. if (nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_user))
  226. ret = do_authinfo_user(server, authusername, authpassword);
  227. }
  228. return (ret == OK_AUTH);
  229. }
  230. authpassword[0] = '\0';
  231. authuser = strncpy(authusername, authuser, sizeof(authusername) - 1);
  232. authpass = authpassword;
  233. /*
  234. * No username/password given yet.
  235. * Read .newsauth only if we had not failed authentication yet for the
  236. * current server (we don't want to try wrong username/password pairs
  237. * more than once because this may lead to an infinite loop at connection
  238. * startup: nntp_open tries to authenticate, it fails, server closes
  239. * connection; next time tin tries to access the server it will do
  240. * nntp_open again ...). This means, however, that if configuration
  241. * changed on the server between two authentication attempts tin will
  242. * prompt you the second time instead of reading .newsauth (except when
  243. * at startup time; in this case, it will just leave); you have to leave
  244. * and restart tin or change to another server and back in order to get
  245. * it read again.
  246. */
  247. if ((changed || !initialized) && !already_failed) {
  248. if (read_newsauth_file(server, authuser, authpass)) {
  249. # ifdef USE_SASL
  250. if (nntp_caps.sasl & SASL_PLAIN)
  251. ret = do_authinfo_sasl_plain(authuser, authpass);
  252. if (ret != OK_AUTH)
  253. # endif /* USE_SASL */
  254. {
  255. if (force_auth_on_conn_open || nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_user))
  256. ret = do_authinfo_user(server, authuser, authpass);
  257. }
  258. already_failed = (ret != OK_AUTH);
  259. if (ret == OK_AUTH) {
  260. # ifdef DEBUG
  261. if (debug & DEBUG_NNTP)
  262. debug_print_file("NNTP", "authorization succeeded");
  263. # endif /* DEBUG */
  264. initialized = TRUE;
  265. return TRUE;
  266. }
  267. }
  268. }
  269. /*
  270. * At this point, either authentication with username/password pair from
  271. * .newsauth has failed or there's no .newsauth file respectively no
  272. * matching username/password for the current server. If we are not at
  273. * startup we ask the user to enter such a pair by hand. Don't ask him
  274. * startup except if requested by -A option because if he doesn't need
  275. * authenticate(we don't know), the "Server expects authentication"
  276. * messages are annoying (and even wrong).
  277. * UNSURE: Maybe we want to make this decision configurable in the
  278. * options menu, too, so that the user doesn't need -A.
  279. * TODO: Put questions into do_authinfo_user() because it is possible
  280. * that the server doesn't want a password; so only ask for it if needed.
  281. */
  282. if (force_auth_on_conn_open || !startup) {
  283. if (nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_state && ((nntp_caps.sasl & SASL_PLAIN) || nntp_caps.authinfo_user || (!nntp_caps.authinfo_user && !(nntp_caps.sasl & SASL_PLAIN))))) {
  284. # ifdef USE_CURSES
  285. int state = RawState();
  286. # endif /* USE_CURSES */
  287. wait_message(0, _(txt_auth_needed));
  288. # ifdef USE_CURSES
  289. Raw(TRUE);
  290. # endif /* USE_CURSES */
  291. if (!prompt_default_string(_(txt_auth_user), authuser, PATH_LEN, authusername, HIST_NONE)) {
  292. # ifdef DEBUG
  293. if (debug & DEBUG_NNTP)
  294. debug_print_file("NNTP", "authorization failed: no username");
  295. # endif /* DEBUG */
  296. return FALSE;
  297. }
  298. # ifdef USE_CURSES
  299. Raw(state);
  300. my_printf("%s", _(txt_auth_pass));
  301. wgetnstr(stdscr, authpassword, sizeof(authpassword));
  302. Raw(TRUE);
  303. # else
  304. /*
  305. * on some systems (i.e. Solaris) getpass(3) is limited to 8 chars ->
  306. * we use tin_getline()
  307. */
  308. authpass = strncpy(authpassword, tin_getline(_(txt_auth_pass), FALSE, NULL, PATH_LEN, TRUE, HIST_NONE), sizeof(authpassword) - 1);
  309. # endif /* USE_CURSES */
  310. # ifdef USE_SASL
  311. if (nntp_caps.sasl & SASL_PLAIN)
  312. ret = do_authinfo_sasl_plain(authuser, authpass);
  313. if (ret != OK_AUTH)
  314. # endif /* USE_SASL */
  315. {
  316. if (nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && (nntp_caps.authinfo_user || (!nntp_caps.authinfo_user && !nntp_caps.authinfo_sasl)))) {
  317. # ifdef DEBUG
  318. if (debug & DEBUG_NNTP) {
  319. if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_sasl && !nntp_caps.authinfo_user)
  320. debug_print_file("NNTP", "!!! No supported authmethod available, trying AUTHINFO USER/PASS");
  321. }
  322. # endif /* DEBUG */
  323. ret = do_authinfo_user(server, authuser, authpass);
  324. if (ret != OK_AUTH)
  325. already_failed = TRUE;
  326. /*
  327. * giganews once responded to CAPABILITIES with just
  328. * "VERSION 2", no mode-switching indication, no reader
  329. * indication, no post indication, no authentication
  330. * indication, ... so in case AUTHINFO USER/PASS succeeds
  331. * if not advertized we simply go on but fully ignore
  332. * CAPABILITIES
  333. */
  334. if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_user && !nntp_caps.authinfo_sasl && ret == OK_AUTH)
  335. nntp_caps.type = BROKEN;
  336. }
  337. }
  338. initialized = TRUE;
  339. my_retouch(); /* Get rid of the chaff */
  340. } else {
  341. /*
  342. * TODO:
  343. * nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_state
  344. * can we change the sate here? and if so how? SARTTLS? MODE
  345. * READER?
  346. */
  347. # ifdef DEBUG
  348. if (debug & DEBUG_NNTP)
  349. debug_print_file("NNTP", "authorization not allowed in current sate");
  350. # endif /* DEBUG */
  351. /*
  352. * we return OK_AUTH here once so tin doesn't exit just because a
  353. * single command requested auth ...
  354. */
  355. if (!already_failed)
  356. ret = OK_AUTH;
  357. }
  358. }
  359. # ifdef DEBUG
  360. if (debug & DEBUG_NNTP)
  361. debug_print_file("NNTP", "authorization %s", (ret == OK_AUTH ? "succeeded" : "failed"));
  362. # endif /* DEBUG */
  363. return (ret == OK_AUTH);
  364. }
  365. /*
  366. * Do authentication stuff. Return TRUE if authentication was successful,
  367. * FALSE otherwise.
  368. *
  369. * try ORIGINAL AUTHINFO method.
  370. * Other authentication methods can easily be added.
  371. */
  372. t_bool
  373. authenticate(
  374. char *server,
  375. char *user,
  376. t_bool startup)
  377. {
  378. return authinfo_plain(server, user, startup);
  379. }
  380. # ifdef USE_SASL
  381. static int
  382. do_authinfo_sasl_plain(
  383. char *authuser,
  384. char *authpass)
  385. {
  386. char line[PATH_LEN];
  387. char *foo;
  388. int ret;
  389. # ifdef DEBUG
  390. if (debug & DEBUG_NNTP)
  391. debug_print_file("NNTP", "do_authinfo_sasl_plain(%s, %s)", BlankIfNull(authuser), BlankIfNull(authpass));
  392. # endif /* DEBUG */
  393. if ((foo = sasl_auth_plain(authuser, authpass)) == NULL)
  394. return ERR_AUTHBAD;
  395. snprintf(line, sizeof(line), "AUTHINFO SASL PLAIN %s", foo);
  396. free(foo);
  397. # ifdef DEBUG
  398. if (debug & DEBUG_NNTP)
  399. debug_print_file("NNTP", "authorization %s", line);
  400. # endif /* DEBUG */
  401. put_server(line);
  402. ret = get_only_respcode(line, sizeof(line));
  403. if (!batch_mode || verbose || ret != OK_AUTH)
  404. wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  405. return ret;
  406. }
  407. static char *sasl_auth_plain(
  408. char *user,
  409. char *pass)
  410. {
  411. Gsasl *ctx = NULL;
  412. Gsasl_session *session;
  413. char *p = NULL;
  414. const char *mech = "PLAIN";
  415. if (gsasl_init(&ctx) != GSASL_OK) /* TODO: do this only once at startup */
  416. return p;
  417. if (gsasl_client_start(ctx, mech, &session) != GSASL_OK) {
  418. gsasl_done(ctx);
  419. return p;
  420. }
  421. gsasl_property_set(session, GSASL_AUTHID, user);
  422. gsasl_property_set(session, GSASL_PASSWORD, pass);
  423. if (gsasl_step64(session, NULL, &p) != GSASL_OK)
  424. FreeAndNull(p);
  425. gsasl_finish(session);
  426. gsasl_done(ctx);
  427. return p;
  428. }
  429. # endif /* USE_SASL */
  430. #else
  431. static void no_authenticate(void); /* proto-type */
  432. static void
  433. no_authenticate( /* ANSI C requires non-empty source file */
  434. void)
  435. {
  436. }
  437. #endif /* NNTP_ABLE */