"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.2/src/auth.c" (9 Dec 2022, 18213 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "auth.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
2.6.1_vs_2.6.2.
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 : 2022-06-16
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-2023 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 *
17 * 1. Redistributions of source code must retain the above copyright notice,
18 * this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
23 *
24 * 3. Neither the name of the copyright holder nor the names of its
25 * contributors may be used to endorse or promote products derived from
26 * this software without specific prior written permission.
27 *
28 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
32 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 * POSSIBILITY OF SUCH DAMAGE.
39 */
40
41
42 #ifndef TIN_H
43 # include "tin.h"
44 #endif /* !TIN_H */
45 #ifndef TCURSES_H
46 # include "tcurses.h"
47 #endif /* !TCURSES_H */
48
49
50 /*
51 * we don't need authentication stuff at all if we don't have NNTP support
52 */
53 #ifdef NNTP_ABLE
54 /*
55 * local prototypes
56 */
57 static int do_authinfo_user(char *server, char *authuser, char *authpass);
58 static t_bool read_newsauth_file(char *server, char *authuser, char *authpass);
59 static t_bool authinfo_plain(char *server, char *authuser, t_bool startup);
60 # ifdef USE_SASL
61 static char *sasl_auth_plain(char *user, char *pass);
62 static int do_authinfo_sasl_plain(char *authuser, char *authpass);
63 # endif /* USE_SASL */
64
65
66 /*
67 * Read the ${TIN_HOMEDIR:-"$HOME"}/.newsauth file and put authentication
68 * username and password for the specified server in the given strings.
69 * Returns TRUE if at least a password was found, FALSE if there was
70 * no .newsauth file or no matching server.
71 */
72 static t_bool
73 read_newsauth_file(
74 char *server,
75 char *authuser,
76 char *authpass)
77 {
78 FILE *fp;
79 char *_authpass;
80 char *ptr;
81 char filename[PATH_LEN];
82 char line[PATH_LEN];
83 int found = 0;
84 int fd;
85 struct stat statbuf;
86
87 joinpath(filename, sizeof(filename), homedir, ".newsauth");
88
89 if ((fp = fopen(filename, "r"))) {
90 if ((fd = fileno(fp)) == -1) {
91 fclose(fp);
92 return FALSE;
93 }
94 if (fstat(fd, &statbuf) == -1) {
95 fclose(fp);
96 return FALSE;
97 }
98
99 # ifndef FILE_MODE_BROKEN
100 if (S_ISREG(statbuf.st_mode) && (statbuf.st_mode|S_IRUSR|S_IWUSR) != (S_IRUSR|S_IWUSR|S_IFREG)) {
101 error_message(4, _(txt_error_insecure_permissions), filename, statbuf.st_mode);
102 /*
103 * TODO: fix permissions?
104 * #ifdef HAVE_FCHMOD
105 * fchmod(fd, S_IRUSR|S_IWUSR);
106 * #else
107 * # ifdef HAVE_CHMOD
108 * chmod(filename, S_IRUSR|S_IWUSR);
109 * # endif
110 * #endif
111 */
112 }
113 # endif /* !FILE_MODE_BROKEN */
114
115 /*
116 * Search through authorization file for correct NNTP server
117 * File has format: 'nntp-server' 'password' ['username']
118 */
119 while (fgets(line, sizeof(line), fp) != NULL) {
120 /* strip trailing newline character */
121 ptr = strchr(line, '\n');
122 if (ptr != NULL)
123 *ptr = '\0';
124
125 /* Get server from 1st part of the line */
126 ptr = strpbrk(line, " \t");
127
128 if (ptr == NULL) /* no passwd, no auth, skip */
129 continue;
130
131 *ptr++ = '\0'; /* cut off server part */
132
133 if ((strcasecmp(line, server)))
134 continue; /* wrong server, keep on */
135
136 /* Get password from 2nd part of the line */
137 while (*ptr == ' ' || *ptr == '\t')
138 ptr++; /* skip any blanks */
139
140 _authpass = ptr;
141
142 if (*_authpass == '"') { /* skip "embedded" password string */
143 ptr = strrchr(_authpass, '"');
144 if ((ptr != NULL) && (ptr > _authpass)) {
145 _authpass++;
146 *ptr++ = '\0'; /* cut off trailing " */
147 } else /* no matching ", proceed as normal */
148 ptr = _authpass;
149 }
150
151 /* Get user from 3rd part of the line */
152 ptr = strpbrk(ptr, " \t"); /* find next separating blank */
153
154 if (ptr != NULL) { /* a 3rd argument follows */
155 while (*ptr == ' ' || *ptr == '\t') /* skip any blanks */
156 *ptr++ = '\0';
157 if (*ptr != '\0') /* if it is not just empty */
158 strcpy(authuser, ptr); /* so will replace default user */
159 }
160 strcpy(authpass, _authpass);
161 found++;
162 break; /* if we end up here, everything seems OK */
163 }
164 fclose(fp);
165 return (found > 0);
166 }
167 return FALSE;
168 }
169
170
171 /*
172 * Perform authentication with AUTHINFO USER method. Return response
173 * code from server.
174 *
175 * we don't handle ERR_ENCRYPT right now
176 *
177 * we don't convert authuser and authpass to UTF-8 as required by 3977
178 * and we do in do_authinfo_sasl_plain(); if we want todo so it should
179 * lkely be done in authinfo_plain() instead.
180 */
181 static int
182 do_authinfo_user(
183 char *server,
184 char *authuser,
185 char *authpass)
186 {
187 char line[PATH_LEN];
188 int ret;
189
190 /* may violate RFC 3977 3.1; use MIN(NNTP_STRLEN, sizeof(line)) ? */
191 snprintf(line, sizeof(line), "AUTHINFO USER %s", authuser);
192 # ifdef DEBUG
193 if ((debug & DEBUG_NNTP) && verbose > 1)
194 debug_print_file("NNTP", "authorization %s", line);
195 # endif /* DEBUG */
196 put_server(line);
197 if ((ret = get_only_respcode(NULL, 0)) != NEED_AUTHDATA)
198 return ret;
199
200 if ((authpass == NULL) || (*authpass == '\0')) {
201 # ifdef DEBUG
202 if ((debug & DEBUG_NNTP) && verbose > 1)
203 debug_print_file("NNTP", "authorization failed: no password");
204 # endif /* DEBUG */
205 error_message(2, _(txt_auth_failed_nopass), server);
206 return ERR_AUTHBAD;
207 }
208
209 /* may violate RFC 3977 3.1; use MIN(NNTP_STRLEN, sizeof(line)) ? */
210 snprintf(line, sizeof(line), "AUTHINFO PASS %s", authpass);
211 # ifdef DEBUG
212 if ((debug & DEBUG_NNTP) && verbose > 1)
213 debug_print_file("NNTP", "authorization %s", line);
214 # endif /* DEBUG */
215 put_server(line);
216 ret = get_only_respcode(line, sizeof(line));
217 if (!batch_mode || verbose || ret != OK_AUTH)
218 wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
219 return ret;
220 }
221
222
223 /*
224 * NNTP user authorization. Returns TRUE if authorization succeeded,
225 * FALSE if not.
226 *
227 * tries AUTHINFO SASL PLAIN (if available) fist and if not successful
228 * AUTHINFO USER/PASS
229 *
230 * If username/passwd already given, and server wasn't changed, retry those.
231 * Otherwise, read password from ~/.newsauth or, if not present or no matching
232 * server found, from console.
233 *
234 * The ~/.newsauth authorization file has the format:
235 * nntpserver1 password [user]
236 * nntpserver2 password [user]
237 * etc.
238 *
239 * TODO: convert authuser and authpass to UTF-8 as required by 3977?
240 */
241 static t_bool
242 authinfo_plain(
243 char *server,
244 char *authuser,
245 t_bool startup)
246 {
247 char *authpass;
248 int ret = ERR_AUTHBAD, changed;
249 static char authusername[PATH_LEN] = "";
250 static char authpassword[PATH_LEN] = "";
251 static char last_server[PATH_LEN] = "";
252 static t_bool already_failed = FALSE;
253 static t_bool initialized = FALSE;
254
255 if ((changed = strcmp(server, last_server))) /* do we need new auth values? */
256 STRCPY(last_server, server);
257
258 /*
259 * Let's try the previous auth pair first, if applicable.
260 * Else, proceed to the other mechanisms.
261 */
262 if (initialized && !changed && !already_failed) {
263 # ifdef USE_SASL
264 if (nntp_caps.sasl & SASL_PLAIN)
265 ret = do_authinfo_sasl_plain(authusername, authpassword);
266 if (ret != OK_AUTH)
267 # endif /* USE_SASL */
268 {
269 if (nntp_caps.type != CAPABILITIES || nntp_caps.authinfo_user)
270 ret = do_authinfo_user(server, authusername, authpassword);
271 }
272 return (ret == OK_AUTH);
273 }
274
275 authpassword[0] = '\0';
276 STRCPY(authusername, authuser);
277 authuser = authusername;
278 authpass = authpassword;
279
280 /*
281 * No username/password given yet.
282 * Read .newsauth only if we had not failed authentication yet for the
283 * current server (we don't want to try wrong username/password pairs
284 * more than once because this may lead to an infinite loop at connection
285 * startup: nntp_open tries to authenticate, it fails, server closes
286 * connection; next time tin tries to access the server it will do
287 * nntp_open again ...). This means, however, that if configuration
288 * changed on the server between two authentication attempts tin will
289 * prompt you the second time instead of reading .newsauth (except when
290 * at startup time; in this case, it will just leave); you have to leave
291 * and restart tin or change to another server and back in order to get
292 * it read again.
293 */
294 if ((changed || !initialized) && !already_failed) {
295 if (read_newsauth_file(server, authuser, authpass)) {
296 # ifdef USE_SASL
297 if (nntp_caps.sasl & SASL_PLAIN)
298 ret = do_authinfo_sasl_plain(authuser, authpass);
299
300 if (ret != OK_AUTH)
301 # endif /* USE_SASL */
302 {
303 if (force_auth_on_conn_open || nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_user))
304 ret = do_authinfo_user(server, authuser, authpass);
305 }
306 already_failed = (ret != OK_AUTH);
307
308 if (ret == OK_AUTH) {
309 # ifdef DEBUG
310 if ((debug & DEBUG_NNTP) && verbose > 1)
311 debug_print_file("NNTP", "authorization succeeded");
312 # endif /* DEBUG */
313 initialized = TRUE;
314 return TRUE;
315 }
316 }
317 # ifdef DEBUG
318 else {
319 if ((debug & DEBUG_NNTP) && verbose > 1)
320 debug_print_file("NNTP", "read_newsauth_file(\"%s\", \"%s\", \"%s\") failed", server, authuser, authpass);
321 }
322 # endif /* DEBUG */
323 }
324
325 /*
326 * At this point, either authentication with username/password pair from
327 * .newsauth has failed or there's no .newsauth file respectively no
328 * matching username/password for the current server. If we are not at
329 * startup we ask the user to enter such a pair by hand. Don't ask him
330 * at startup except if requested by -A option because if he doesn't need
331 * to authenticate (we don't know), the "Server expects authentication"
332 * messages are annoying (and even wrong).
333 * UNSURE: Maybe we want to make this decision configurable in the
334 * options menu, too, so that the user doesn't need -A.
335 * TODO: Put questions into do_authinfo_user() because it is possible
336 * that the server doesn't want a password; so only ask for it if needed.
337 */
338 if (force_auth_on_conn_open || !startup) {
339 if (batch_mode) { /* no interactive username/password prompting */
340 error_message(0, _(txt_auth_needed));
341 return (ret == OK_AUTH);
342 }
343 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))))) {
344 # ifdef USE_CURSES
345 int state = RawState();
346 # endif /* USE_CURSES */
347
348 wait_message(0, _(txt_auth_needed));
349 # ifdef USE_CURSES
350 Raw(TRUE);
351 # endif /* USE_CURSES */
352 if (!prompt_default_string(_(txt_auth_user), authuser, sizeof(authusername) - 1, authusername, HIST_NONE)) {
353 # ifdef DEBUG
354 if ((debug & DEBUG_NNTP) && verbose > 1)
355 debug_print_file("NNTP", "authorization failed: no username");
356 # endif /* DEBUG */
357 return FALSE;
358 }
359
360 # ifdef USE_CURSES
361 Raw(state);
362 my_printf("%s", _(txt_auth_pass));
363 wgetnstr(stdscr, authpassword, sizeof(authpassword) - 1);
364 authpassword[sizeof(authpassword) - 1] = '\0';
365 Raw(TRUE);
366 # else
367 /*
368 * on some systems (i.e. Solaris) getpass(3) is limited
369 * to 8 chars -> we use tin_getline()
370 */
371 STRCPY(authpassword, tin_getline(_(txt_auth_pass), 0, NULL, sizeof(authpassword) - 1, TRUE, HIST_NONE));
372 # endif /* USE_CURSES */
373
374 # ifdef USE_SASL
375 if (nntp_caps.sasl & SASL_PLAIN)
376 ret = do_authinfo_sasl_plain(authuser, authpass);
377 if (ret != OK_AUTH)
378 # endif /* USE_SASL */
379 {
380 if (nntp_caps.type != CAPABILITIES || (nntp_caps.authinfo_user || !nntp_caps.authinfo_sasl)) {
381 # ifdef DEBUG
382 if (debug & DEBUG_NNTP) {
383 if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_sasl && !nntp_caps.authinfo_user)
384 debug_print_file("NNTP", "!!! No supported authmethod available, trying AUTHINFO USER/PASS");
385 }
386 # endif /* DEBUG */
387 ret = do_authinfo_user(server, authuser, authpass);
388 if (ret != OK_AUTH)
389 already_failed = TRUE;
390 /*
391 * giganews once responded to CAPABILITIES with just
392 * "VERSION 2", no mode-switching indication, no reader
393 * indication, no post indication, no authentication
394 * indication, ... so in case AUTHINFO USER/PASS succeeds
395 * if not advertised we simply go on but fully ignore
396 * CAPABILITIES
397 */
398 if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_user && !nntp_caps.authinfo_sasl && ret == OK_AUTH)
399 nntp_caps.type = BROKEN;
400 }
401 }
402 initialized = TRUE;
403 my_retouch(); /* Get rid of the chaff */
404 } else {
405 /*
406 * TODO:
407 * nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_state
408 * can we change the state here? and if so how? SARTTLS? MODE
409 * READER?
410 */
411 # ifdef DEBUG
412 if ((debug & DEBUG_NNTP) && verbose > 1) {
413 debug_print_file("NNTP", "authorization not allowed in current state:");
414 debug_print_file("NNTP", "\tCAPABILITIES: %s", nntp_caps.type ? (nntp_caps.type < 2 ? "CAPABILITIES" : "BROKEN" ) : "NONE");
415 debug_print_file("NNTP", "\t%cREADER, %cMODE READER", nntp_caps.reader ? '+' : '-', nntp_caps.mode_reader ? '+' : '-');
416 debug_print_file("NNTP", "\t%cSTARTTLS", nntp_caps.starttls ? '+' : '-');
417 debug_print_file("NNTP", "\t%cAUTHINFO %s%s", nntp_caps.authinfo_state ? '+' : '-', nntp_caps.authinfo_user ? "USER " : "" , nntp_caps.authinfo_sasl ? "SASL" : "");
418 }
419 # endif /* DEBUG */
420 /*
421 * we return OK_AUTH here once so tin doesn't exit just because a
422 * single command requested auth ...
423 */
424 if (!already_failed)
425 ret = OK_AUTH;
426 }
427 }
428
429 # ifdef DEBUG
430 if ((debug & DEBUG_NNTP) && verbose > 1)
431 debug_print_file("NNTP", "authorization %s", (ret == OK_AUTH ? "succeeded" : "failed"));
432 # endif /* DEBUG */
433
434 return (ret == OK_AUTH);
435 }
436
437
438 /*
439 * Do authentication stuff. Return TRUE if authentication was successful,
440 * FALSE otherwise.
441 *
442 * try ORIGINAL AUTHINFO method.
443 * Other authentication methods can easily be added.
444 */
445 t_bool
446 authenticate(
447 char *server,
448 char *user,
449 t_bool startup)
450 {
451 char line[NNTP_STRLEN];
452 t_bool ret;
453
454 ret = authinfo_plain(server, user, startup);
455
456 if (ret && nntp_caps.type == CAPABILITIES) {
457 /* resend CAPABILITIES, but "manually" to avoid AUTH loop */
458 snprintf(line, sizeof(line), "%s", "CAPABILITIES");
459 # ifdef DEBUG
460 if ((debug & DEBUG_NNTP) && verbose > 1)
461 debug_print_file("NNTP", "authenticate(%s)", line);
462 # endif /* DEBUG */
463 put_server(line);
464
465 check_extensions(get_only_respcode(line, sizeof(line)));
466 }
467
468 return ret;
469 }
470
471
472 # ifdef USE_SASL
473 static int
474 do_authinfo_sasl_plain(
475 char *authuser,
476 char *authpass)
477 {
478 char line[PATH_LEN];
479 char *foo;
480 char *utf8user;
481 char *utf8pass;
482 int ret;
483 # ifdef CHARSET_CONVERSION
484 char *cp;
485 int i, c = 0;
486 t_bool contains_8bit = FALSE;
487 # endif /* CHARSET_CONVERSION */
488
489 utf8user = my_strdup(authuser);
490 utf8pass = my_strdup(authpass);
491 # ifdef CHARSET_CONVERSION
492 /* RFC 4616 */
493 if (!IS_LOCAL_CHARSET("UTF-8")) {
494 for (cp = utf8user; *cp && !contains_8bit; cp++) {
495 if (!isascii(*cp)) {
496 contains_8bit = TRUE;
497 break;
498 }
499 }
500 for (cp = utf8pass; *cp && !contains_8bit; cp++) {
501 if (!isascii(*cp)) {
502 contains_8bit = TRUE;
503 break;
504 }
505 }
506 if (contains_8bit) {
507 for (i = 0; txt_mime_charsets[i] != NULL; i++) {
508 if (!strcasecmp("UTF-8", txt_mime_charsets[i])) {
509 c = i;
510 break;
511 }
512 }
513 if (c == i) { /* should never fail */
514 if (!buffer_to_network(utf8user, c)) {
515 free(utf8user);
516 utf8user = my_strdup(authuser);
517 }
518 if (!buffer_to_network(utf8pass, c)) {
519 free(utf8pass);
520 utf8pass = my_strdup(authpass);
521 }
522 }
523 }
524 }
525 # endif /* CHARSET_CONVERSION */
526
527 # ifdef DEBUG
528 if ((debug & DEBUG_NNTP) && verbose > 1)
529 debug_print_file("NNTP", "do_authinfo_sasl_plain(\"%s\", \"%s\")", BlankIfNull(authuser), BlankIfNull(authpass));
530 # endif /* DEBUG */
531
532 if ((foo = sasl_auth_plain(utf8user, utf8pass)) == NULL) {
533 free(utf8user);
534 free(utf8pass);
535 return ERR_AUTHBAD;
536 }
537
538 free(utf8user);
539 free(utf8pass);
540
541 snprintf(line, sizeof(line), "AUTHINFO SASL PLAIN %s", foo);
542 FreeIfNeeded(foo);
543 # ifdef DEBUG
544 if ((debug & DEBUG_NNTP) && verbose > 1)
545 debug_print_file("NNTP", "authorization %s", line);
546 # endif /* DEBUG */
547 put_server(line);
548 ret = get_only_respcode(line, sizeof(line));
549 if (!batch_mode || verbose || ret != OK_AUTH)
550 wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
551 return ret;
552 }
553
554
555 static char *
556 sasl_auth_plain(
557 char *user,
558 char *pass)
559 {
560 Gsasl *ctx = NULL;
561 Gsasl_session *session;
562 char *p = NULL;
563 const char *mech = "PLAIN";
564
565 if (gsasl_init(&ctx) != GSASL_OK) /* TODO: do this only once at startup */
566 return p;
567 if (gsasl_client_start(ctx, mech, &session) != GSASL_OK) {
568 gsasl_done(ctx);
569 return p;
570 }
571 /* GSASL_AUTHZID (authorization identity) is (usually) unused with NNTP */
572 gsasl_property_set(session, GSASL_AUTHID, user); /* authentication identity */
573 gsasl_property_set(session, GSASL_PASSWORD, pass); /* password of the authentication identity */
574 if (gsasl_step64(session, NULL, &p) != GSASL_OK)
575 FreeAndNull(p);
576 gsasl_finish(session);
577 gsasl_done(ctx);
578 return p;
579 }
580 # endif /* USE_SASL */
581
582 #else
583 static void no_authenticate(void); /* proto-type */
584 static void
585 no_authenticate( /* ANSI C requires non-empty source file */
586 void)
587 {
588 }
589 #endif /* NNTP_ABLE */