File: | examples/openid20/smtp-server-openid20.c |
Warning: | line 229, column 2 Value stored to 'rc' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* smtp-server-openid20.c --- Example SMTP server with OpenID 2.0 support |
2 | * Copyright (C) 2012-2025 Simon Josefsson |
3 | * |
4 | * This file is part of GNU SASL. |
5 | * |
6 | * This program is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | * |
19 | */ |
20 | |
21 | #include <config.h> |
22 | |
23 | /* This is based on ../smtp-server.c but adds support for OpenID 2.0. |
24 | See README for instructions. */ |
25 | |
26 | /* This is a minimal SMTP server with GNU SASL authentication support. |
27 | The only valid password is "sesam". This server will complete |
28 | authentications using LOGIN, PLAIN, DIGEST-MD5, CRAM-MD5, and |
29 | SCRAM-SHA-1. It accepts an optional command line parameter |
30 | specifying the service name (i.e., a numerical port number or |
31 | /etc/services name). By default it listens on port "2000". */ |
32 | |
33 | #include <stdio.h> |
34 | #include <unistd.h> |
35 | #include <time.h> |
36 | #include <sys/stat.h> |
37 | #include <sys/types.h> |
38 | |
39 | #include <string.h> |
40 | #include <stdlib.h> |
41 | #include <stdarg.h> |
42 | #include <netdb.h> |
43 | #include <signal.h> |
44 | |
45 | #include <gsasl.h> |
46 | |
47 | static char *store_path; |
48 | static char *realm; |
49 | static char *return_to; |
50 | |
51 | static void |
52 | hex_encode (char *dst, const char *src, size_t len) |
53 | { |
54 | static const char trans[] = "0123456789abcdef"; |
55 | |
56 | while (len--) |
57 | { |
58 | *dst++ = trans[(*src >> 4) & 0xf]; |
59 | *dst++ = trans[*src++ & 0xf]; |
60 | } |
61 | |
62 | *dst = '\0'; |
63 | } |
64 | |
65 | static int |
66 | write_file (const char *filename, const char *data) |
67 | { |
68 | FILE *fh; |
69 | int rc = 0; |
70 | |
71 | fh = fopen (filename, "w"); |
72 | if (!fh) |
73 | { |
74 | perror ("fopen"); |
75 | return -1; |
76 | } |
77 | |
78 | if (fputs (data, fh) <= 0) |
79 | rc = -1; |
80 | |
81 | if (fclose (fh) != 0) |
82 | return -1; |
83 | |
84 | return rc; |
85 | } |
86 | |
87 | static char * |
88 | get_redirect_url (Gsasl_session *sctx) |
89 | { |
90 | FILE *fh; |
91 | char *tmp, *tmp2; |
92 | char *line = NULL((void*)0); |
93 | size_t n = 0; |
94 | const char *nonce = gsasl_session_hook_get (sctx); |
95 | int rc; |
96 | |
97 | rc = asprintf (&tmp, "%s", store_path); |
98 | if (rc <= 0) |
99 | { |
100 | perror ("asprintf"); |
101 | return NULL((void*)0); |
102 | } |
103 | mkdir (tmp, 0770); |
104 | free (tmp); |
105 | |
106 | rc = asprintf (&tmp, "%s/state", store_path); |
107 | if (rc <= 0) |
108 | { |
109 | perror ("asprintf"); |
110 | return NULL((void*)0); |
111 | } |
112 | mkdir (tmp, 0770); |
113 | free (tmp); |
114 | |
115 | rc = asprintf (&tmp, "%s/state/%s", store_path, nonce); |
116 | if (rc <= 0) |
117 | { |
118 | perror ("asprintf"); |
119 | return NULL((void*)0); |
120 | } |
121 | mkdir (tmp, 0770); |
122 | free (tmp); |
123 | |
124 | rc = asprintf (&tmp, "%s/state/%s/openid_url", store_path, nonce); |
125 | if (rc <= 0) |
126 | { |
127 | perror ("asprintf"); |
128 | return NULL((void*)0); |
129 | } |
130 | if (write_file (tmp, gsasl_property_fast (sctx, GSASL_AUTHID))) |
131 | return NULL((void*)0); |
132 | free (tmp); |
133 | |
134 | rc = asprintf (&tmp, "%s/state/%s/realm", store_path, nonce); |
135 | if (rc <= 0) |
136 | { |
137 | perror ("asprintf"); |
138 | return NULL((void*)0); |
139 | } |
140 | if (write_file (tmp, realm)) |
141 | return NULL((void*)0); |
142 | free (tmp); |
143 | |
144 | rc = asprintf (&tmp, "%s/state/%s/return_to", store_path, nonce); |
145 | if (rc <= 0) |
146 | { |
147 | perror ("asprintf"); |
148 | return NULL((void*)0); |
149 | } |
150 | rc = asprintf (&tmp2, "%s/%s", return_to, nonce); |
151 | if (rc <= 0) |
152 | { |
153 | perror ("asprintf"); |
154 | return NULL((void*)0); |
155 | } |
156 | if (write_file (tmp, tmp2)) |
157 | return NULL((void*)0); |
158 | free (tmp); |
159 | free (tmp2); |
160 | |
161 | rc = |
162 | asprintf (&tmp, "gsasl-openid20-redirect.php %s %s", store_path, nonce); |
163 | if (rc <= 0) |
164 | { |
165 | perror ("asprintf"); |
166 | return NULL((void*)0); |
167 | } |
168 | fh = popen (tmp, "r"); |
169 | free (tmp); |
170 | if (!fh) |
171 | { |
172 | perror ("popen"); |
173 | return NULL((void*)0); |
174 | } |
175 | while (getline (&line, &n, fh) >= 0) |
176 | printf ("gsasl-openid20-redirect.php: %s", line); |
177 | pclose (fh); |
178 | |
179 | rc = asprintf (&tmp, "%s/state/%s/redirect_url", store_path, nonce); |
180 | if (rc <= 0) |
181 | { |
182 | perror ("asprintf"); |
183 | return NULL((void*)0); |
184 | } |
185 | fh = fopen (tmp, "r"); |
186 | if (!fh) |
187 | { |
188 | perror ("fopen"); |
189 | return NULL((void*)0); |
190 | } |
191 | if (getline (&line, &n, fh) <= 0) |
192 | { |
193 | perror ("getline"); |
194 | return NULL((void*)0); |
195 | } |
196 | fclose (fh); |
197 | |
198 | return line; |
199 | } |
200 | |
201 | static int |
202 | callback (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) |
203 | { |
204 | int rc = GSASL_NO_CALLBACK; |
205 | FILE *fh; |
206 | char *line = NULL((void*)0); |
207 | size_t n = 0; |
208 | const char *nonce = gsasl_session_hook_get (sctx); |
209 | char *tmp; |
210 | |
211 | (void) ctx; |
212 | |
213 | switch (prop) |
214 | { |
215 | case GSASL_OPENID20_REDIRECT_URL: |
216 | { |
217 | line = get_redirect_url (sctx); |
218 | if (line == NULL((void*)0)) |
219 | rc = GSASL_AUTHENTICATION_ERROR; |
220 | else |
221 | rc = gsasl_property_set (sctx, prop, line); |
222 | } |
223 | break; |
224 | |
225 | case GSASL_VALIDATE_OPENID20: |
226 | { |
227 | time_t start = time (NULL((void*)0)); |
228 | |
229 | rc = GSASL_AUTHENTICATION_ERROR; |
Value stored to 'rc' is never read | |
230 | do |
231 | { |
232 | sleep (1); |
233 | |
234 | rc = asprintf (&tmp, "%s/state/%s/success", store_path, nonce); |
235 | if (rc <= 0) |
236 | { |
237 | perror ("asprintf"); |
238 | break; |
239 | } |
240 | fh = fopen (tmp, "r"); |
241 | free (tmp); |
242 | if (!fh) |
243 | { |
244 | rc = asprintf (&tmp, "%s/state/%s/fail", store_path, nonce); |
245 | if (rc <= 0) |
246 | { |
247 | perror ("asprintf"); |
248 | break; |
249 | } |
250 | fh = fopen (tmp, "r"); |
251 | free (tmp); |
252 | if (!fh) |
253 | { |
254 | puts ("waiting"); |
255 | continue; |
256 | } |
257 | |
258 | if (getline (&line, &n, fh) > 0) |
259 | printf ("fail: %s\n", line); |
260 | fclose (fh); |
261 | |
262 | rc = GSASL_AUTHENTICATION_ERROR; |
263 | break; |
264 | } |
265 | |
266 | if (getline (&line, &n, fh) > 0) |
267 | printf ("claimed id: %s\n", line); |
268 | fclose (fh); |
269 | |
270 | rc = gsasl_property_set (sctx, GSASL_AUTHID, line); |
271 | if (rc != GSASL_OK) |
272 | { |
273 | printf ("Failure in gsasl_property_set (%d): %s\n", |
274 | rc, gsasl_strerror (rc)); |
275 | break; |
276 | } |
277 | |
278 | rc = asprintf (&tmp, "%s/state/%s/sreg", store_path, nonce); |
279 | if (rc <= 0) |
280 | { |
281 | perror ("asprintf"); |
282 | break; |
283 | } |
284 | fh = fopen (tmp, "r"); |
285 | free (tmp); |
286 | if (fh) |
287 | { |
288 | if (getline (&line, &n, fh) > 0) |
289 | { |
290 | printf ("sreg: %s\n", line); |
291 | rc = gsasl_property_set (sctx, |
292 | GSASL_OPENID20_OUTCOME_DATA, |
293 | line); |
294 | if (rc != GSASL_OK) |
295 | { |
296 | printf ("Failure in gsasl_property_set (%d): %s\n", |
297 | rc, gsasl_strerror (rc)); |
298 | break; |
299 | } |
300 | } |
301 | fclose (fh); |
302 | } |
303 | |
304 | rc = GSASL_OK; |
305 | break; |
306 | } |
307 | while (time (NULL((void*)0)) - start < 30); |
308 | } |
309 | break; |
310 | |
311 | case GSASL_PASSWORD: |
312 | rc = gsasl_property_set (sctx, prop, "sesam"); |
313 | break; |
314 | |
315 | default: |
316 | /* You may want to log (at debug verbosity level) that an |
317 | unknown property was requested here, possibly after filtering |
318 | known rejected property requests. */ |
319 | break; |
320 | } |
321 | |
322 | return rc; |
323 | } |
324 | |
325 | static ssize_t |
326 | gettrimline (char **line, size_t *n, FILE *fh) |
327 | { |
328 | ssize_t s = getline (line, n, fh); |
329 | |
330 | if (s >= 2) |
331 | { |
332 | if ((*line)[strlen (*line) - 1] == '\n') |
333 | (*line)[strlen (*line) - 1] = '\0'; |
334 | if ((*line)[strlen (*line) - 1] == '\r') |
335 | (*line)[strlen (*line) - 1] = '\0'; |
336 | |
337 | printf ("C: %s\n", *line); |
338 | } |
339 | |
340 | return s; |
341 | } |
342 | |
343 | #define print(fh, ...)printf ("S: "), printf (...), fprintf (fh, ...) \ |
344 | printf ("S: "), printf (__VA_ARGS__), fprintf (fh, __VA_ARGS__) |
345 | |
346 | static void |
347 | server_auth (FILE *fh, Gsasl_session *session) |
348 | { |
349 | char *line = NULL((void*)0); |
350 | size_t n = 0; |
351 | char *p; |
352 | int rc; |
353 | /* The nonce value MUST be at least 2^32 large and large enough to |
354 | handle well in excess of the number of concurrent transactions a |
355 | SASL server shall see. */ |
356 | char bin_nonce[32]; |
357 | char nonce[2 * sizeof (bin_nonce) + 1]; |
358 | |
359 | gsasl_nonce (bin_nonce, sizeof (bin_nonce)); |
360 | hex_encode (nonce, bin_nonce, sizeof (bin_nonce)); |
361 | gsasl_session_hook_set (session, nonce); |
362 | |
363 | /* The ordering and the type of checks in the following loop has to |
364 | be adapted for each protocol depending on its SASL properties. |
365 | SMTP is a "server-first" SASL protocol. This implementation do |
366 | not support piggy-backing of the initial client challenge nor |
367 | piggy-backing of the terminating server response. See RFC 2554 |
368 | and RFC 4422 for terminology. That profile results in the |
369 | following loop structure. Ask on the help-gsasl list if you are |
370 | uncertain. */ |
371 | do |
372 | { |
373 | rc = gsasl_step64 (session, line, &p); |
374 | if (rc == GSASL_NEEDS_MORE || (rc == GSASL_OK && p && *p)) |
375 | { |
376 | print (fh, "334 %s\n", p)printf ("S: "), printf ("334 %s\n", p), fprintf (fh, "334 %s\n" , p); |
377 | gsasl_free (p); |
378 | |
379 | if (gettrimline (&line, &n, fh) < 0) |
380 | { |
381 | print (fh, "221 localhost getline failure\n")printf ("S: "), printf ("221 localhost getline failure\n"), fprintf (fh, "221 localhost getline failure\n"); |
382 | goto done; |
383 | } |
384 | } |
385 | } |
386 | while (rc == GSASL_NEEDS_MORE); |
387 | |
388 | if (rc != GSASL_OK) |
389 | { |
390 | print (fh, "535 gsasl_step64 (%d): %s\n", rc, gsasl_strerror (rc))printf ("S: "), printf ("535 gsasl_step64 (%d): %s\n", rc, gsasl_strerror (rc)), fprintf (fh, "535 gsasl_step64 (%d): %s\n", rc, gsasl_strerror (rc)); |
391 | goto done; |
392 | } |
393 | |
394 | { |
395 | const char *authid = gsasl_property_fast (session, GSASL_AUTHID); |
396 | const char *authzid = gsasl_property_fast (session, GSASL_AUTHZID); |
397 | print (fh, "235 OK [authid: %s authzid: %s]\n",printf ("S: "), printf ("235 OK [authid: %s authzid: %s]\n", authid ? authid : "N/A", authzid ? authzid : "N/A"), fprintf (fh, "235 OK [authid: %s authzid: %s]\n" , authid ? authid : "N/A", authzid ? authzid : "N/A") |
398 | authid ? authid : "N/A", authzid ? authzid : "N/A")printf ("S: "), printf ("235 OK [authid: %s authzid: %s]\n", authid ? authid : "N/A", authzid ? authzid : "N/A"), fprintf (fh, "235 OK [authid: %s authzid: %s]\n" , authid ? authid : "N/A", authzid ? authzid : "N/A"); |
399 | } |
400 | |
401 | done: |
402 | free (line); |
403 | } |
404 | |
405 | static void |
406 | smtp (FILE *fh, Gsasl *ctx) |
407 | { |
408 | char *line = NULL((void*)0); |
409 | size_t n = 0; |
410 | int rc; |
411 | |
412 | print (fh, "220 localhost ESMTP GNU SASL smtp-server\n")printf ("S: "), printf ("220 localhost ESMTP GNU SASL smtp-server\n" ), fprintf (fh, "220 localhost ESMTP GNU SASL smtp-server\n"); |
413 | |
414 | while (gettrimline (&line, &n, fh) >= 0) |
415 | { |
416 | if (strncmp (line, "EHLO ", 5) == 0 || strncmp (line, "ehlo ", 5) == 0) |
417 | { |
418 | char *mechlist; |
419 | |
420 | rc = gsasl_server_mechlist (ctx, &mechlist); |
421 | if (rc != GSASL_OK) |
422 | { |
423 | print (fh, "221 localhost gsasl_server_mechlist (%d): %s\n",printf ("S: "), printf ("221 localhost gsasl_server_mechlist (%d): %s\n" , rc, gsasl_strerror (rc)), fprintf (fh, "221 localhost gsasl_server_mechlist (%d): %s\n" , rc, gsasl_strerror (rc)) |
424 | rc, gsasl_strerror (rc))printf ("S: "), printf ("221 localhost gsasl_server_mechlist (%d): %s\n" , rc, gsasl_strerror (rc)), fprintf (fh, "221 localhost gsasl_server_mechlist (%d): %s\n" , rc, gsasl_strerror (rc)); |
425 | goto done; |
426 | } |
427 | |
428 | print (fh, "250-localhost\n")printf ("S: "), printf ("250-localhost\n"), fprintf (fh, "250-localhost\n" ); |
429 | print (fh, "250 AUTH %s\n", mechlist)printf ("S: "), printf ("250 AUTH %s\n", mechlist), fprintf ( fh, "250 AUTH %s\n", mechlist); |
430 | |
431 | gsasl_free (mechlist); |
432 | } |
433 | else if (strncmp (line, "AUTH ", 5) == 0 |
434 | || strncmp (line, "auth ", 5) == 0) |
435 | { |
436 | Gsasl_session *session = NULL((void*)0); |
437 | |
438 | if ((rc = gsasl_server_start (ctx, line + 5, &session)) != GSASL_OK) |
439 | { |
440 | print (fh, "221 localhost gsasl_server_start (%d): %s\n",printf ("S: "), printf ("221 localhost gsasl_server_start (%d): %s\n" , rc, gsasl_strerror (rc)), fprintf (fh, "221 localhost gsasl_server_start (%d): %s\n" , rc, gsasl_strerror (rc)) |
441 | rc, gsasl_strerror (rc))printf ("S: "), printf ("221 localhost gsasl_server_start (%d): %s\n" , rc, gsasl_strerror (rc)), fprintf (fh, "221 localhost gsasl_server_start (%d): %s\n" , rc, gsasl_strerror (rc)); |
442 | goto done; |
443 | } |
444 | |
445 | server_auth (fh, session); |
446 | |
447 | gsasl_finish (session); |
448 | } |
449 | else if (strncmp (line, "QUIT", 4) == 0 |
450 | || strncmp (line, "quit", 4) == 0) |
451 | { |
452 | print (fh, "221 localhost QUIT\n")printf ("S: "), printf ("221 localhost QUIT\n"), fprintf (fh, "221 localhost QUIT\n"); |
453 | goto done; |
454 | } |
455 | else |
456 | print (fh, "500 unrecognized command\n")printf ("S: "), printf ("500 unrecognized command\n"), fprintf (fh, "500 unrecognized command\n"); |
457 | } |
458 | |
459 | print (fh, "221 localhost getline failure\n")printf ("S: "), printf ("221 localhost getline failure\n"), fprintf (fh, "221 localhost getline failure\n"); |
460 | |
461 | done: |
462 | free (line); |
463 | } |
464 | |
465 | int |
466 | main (int argc, char *argv[]) |
467 | { |
468 | const char *service = argc > 1 ? argv[1] : "2000"; |
469 | volatile int run = 1; |
470 | struct addrinfo hints, *addrs; |
471 | int sockfd; |
472 | int rc; |
473 | int yes = 1; |
474 | Gsasl *ctx; |
475 | |
476 | setvbuf (stdoutstdout, NULL((void*)0), _IONBF2, 0); |
477 | |
478 | if (argc != 5) |
479 | { |
480 | printf ("Usage: %s PORT STORE-PATH REALM RETURN-TO\n", argv[0]); |
481 | exit (EXIT_FAILURE1); |
482 | } |
483 | store_path = argv[2]; |
484 | realm = argv[3]; |
485 | return_to = argv[4]; |
486 | |
487 | rc = gsasl_init (&ctx); |
488 | if (rc < 0) |
489 | { |
490 | printf ("gsasl_init (%d): %s\n", rc, gsasl_strerror (rc)); |
491 | exit (EXIT_FAILURE1); |
492 | } |
493 | |
494 | printf ("%s [gsasl header %s library %s]\n", |
495 | argv[0], GSASL_VERSION"2.2.2.3-6d55", gsasl_check_version (NULL((void*)0))); |
496 | |
497 | gsasl_callback_set (ctx, callback); |
498 | |
499 | memset (&hints, 0, sizeof (hints)); |
500 | hints.ai_flags = AI_PASSIVE0x0001 | AI_ADDRCONFIG0x0020; |
501 | hints.ai_socktype = SOCK_STREAMSOCK_STREAM; |
502 | |
503 | rc = getaddrinfo (NULL((void*)0), service, &hints, &addrs); |
504 | if (rc < 0) |
505 | { |
506 | printf ("getaddrinfo: %s\n", gai_strerror (rc)); |
507 | exit (EXIT_FAILURE1); |
508 | } |
509 | |
510 | sockfd = socket (addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol); |
511 | if (sockfd < 0) |
512 | { |
513 | perror ("socket"); |
514 | exit (EXIT_FAILURE1); |
515 | } |
516 | |
517 | if (setsockopt (sockfd, SOL_SOCKET1, SO_REUSEADDR2, &yes, sizeof (yes)) < 0) |
518 | { |
519 | perror ("setsockopt"); |
520 | exit (EXIT_FAILURE1); |
521 | } |
522 | |
523 | rc = bind (sockfd, addrs->ai_addr, addrs->ai_addrlen); |
524 | if (rc < 0) |
525 | { |
526 | perror ("bind"); |
527 | exit (EXIT_FAILURE1); |
528 | } |
529 | |
530 | freeaddrinfo (addrs); |
531 | |
532 | rc = listen (sockfd, SOMAXCONN4096); |
533 | if (rc < 0) |
534 | { |
535 | perror ("listen"); |
536 | exit (EXIT_FAILURE1); |
537 | } |
538 | |
539 | signal (SIGPIPE13, SIG_IGN((__sighandler_t) 1)); |
540 | |
541 | while (run) |
542 | { |
543 | struct sockaddr from; |
544 | socklen_t fromlen = sizeof (from); |
545 | char host[NI_MAXHOST1025]; |
546 | int fd; |
547 | FILE *fh; |
548 | |
549 | fd = accept (sockfd, &from, &fromlen); |
550 | if (fd < 0) |
551 | { |
552 | perror ("accept"); |
553 | continue; |
554 | } |
555 | |
556 | rc = getnameinfo (&from, fromlen, host, sizeof (host), |
557 | NULL((void*)0), 0, NI_NUMERICHOST1); |
558 | if (rc == 0) |
559 | printf ("connection from %s\n", host); |
560 | else |
561 | printf ("getnameinfo: %s\n", gai_strerror (rc)); |
562 | |
563 | fh = fdopen (fd, "w+"); |
564 | if (!fh) |
565 | { |
566 | perror ("fdopen"); |
567 | close (fd); |
568 | continue; |
569 | } |
570 | |
571 | smtp (fh, ctx); |
572 | |
573 | fclose (fh); |
574 | } |
575 | |
576 | close (sockfd); |
577 | gsasl_done (ctx); |
578 | |
579 | return 0; |
580 | } |