SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / auths / pwcheck.c
1 /* SASL server API implementation
2  * Rob Siemborski
3  * Tim Martin
4  * $Id: checkpw.c,v 1.49 2002/03/07 19:14:04 ken3 Exp $
5  */
6 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
7 /* SPDX-License-Identifier: GPL-2.0-only */
8 /*
9  * Copyright (c) 2001 Carnegie Mellon University.  All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  *
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  *
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in
20  *    the documentation and/or other materials provided with the
21  *    distribution.
22  *
23  * 3. The name "Carnegie Mellon University" must not be used to
24  *    endorse or promote products derived from this software without
25  *    prior written permission. For permission or any other legal
26  *    details, please contact
27  *      Office of Technology Transfer
28  *      Carnegie Mellon University
29  *      5000 Forbes Avenue
30  *      Pittsburgh, PA  15213-3890
31  *      (412) 268-4387, fax: (412) 268-7395
32  *      tech-transfer@andrew.cmu.edu
33  *
34  * 4. Redistributions of any form whatsoever must retain the following
35  *    acknowledgment:
36  *    "This product includes software developed by Computing Services
37  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
38  *
39  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
40  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
41  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
42  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
43  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
44  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
45  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
46  */
47
48 /*
49  * Taken from Cyrus-SASL library and adapted by Alexander S. Sabourenkov
50  * Oct 2001 - Apr 2002: Slightly modified by Philip Hazel.
51  * Aug 2003: new code for saslauthd from Alexander S. Sabourenkov incorporated
52  *           by Philip Hazel (minor mods to avoid compiler warnings)
53  * Oct 2006: (PH) removed redundant tests on "reply" being NULL - some were
54  *           missing, and confused someone who was using this code for some
55  *           other purpose. Here in Exim, "reply" is never NULL.
56  *
57  * screwdriver@lxnt.info
58  *
59  */
60
61 /* Originally this module supported only the pwcheck daemon, which is where its
62 name comes from. Nowadays it supports saslauthd as well; pwcheck is in fact
63 deprecated. The definitions of CYRUS_PWCHECK_SOCKET and CYRUS_SASLAUTHD_SOCKET
64 determine whether the facilities are actually supported or not. */
65
66
67 #include "../exim.h"
68 #include "pwcheck.h"
69
70
71 #if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
72
73 #include <sys/uio.h>
74
75 static int retry_read(int, void *, unsigned );
76 static int retry_writev(int, struct iovec *, int );
77 static int read_string(int, uschar **);
78 static int write_string(int, const uschar *, int);
79
80 #endif
81
82
83 /* A dummy function that always fails if pwcheck support is not
84 wanted. */
85
86 #ifndef CYRUS_PWCHECK_SOCKET
87 int pwcheck_verify_password(const char *userid,
88                             const char *passwd,
89                             const char **reply)
90 {
91 *reply = "pwcheck support is not included in this Exim binary";
92 return PWCHECK_FAIL;
93 }
94
95
96 /* This is the real function */
97
98 #else
99
100  /* taken from cyrus-sasl file checkpw.c */
101  /* pwcheck daemon-authenticated login */
102  int pwcheck_verify_password(const char *userid,
103                                   const char *passwd,
104                                   const char **reply)
105  {
106      int s, start, r, n;
107      struct sockaddr_un srvaddr;
108      struct iovec iov[2];
109      static char response[1024];
110
111      *reply = NULL;
112
113      s = socket(AF_UNIX, SOCK_STREAM, 0);
114      if (s == -1) { return PWCHECK_FAIL; }
115
116      memset(CS &srvaddr, 0, sizeof(srvaddr));
117      srvaddr.sun_family = AF_UNIX;
118      strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path));
119      r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
120      if (r == -1) {
121         DEBUG(D_auth)
122             debug_printf("Cannot connect to pwcheck daemon (at '%s')\n",CYRUS_PWCHECK_SOCKET);
123        *reply = "cannot connect to pwcheck daemon";
124        return PWCHECK_FAIL;
125      }
126
127      iov[0].iov_base = CS userid;
128      iov[0].iov_len = strlen(userid)+1;
129      iov[1].iov_base = CS passwd;
130      iov[1].iov_len = strlen(passwd)+1;
131
132      retry_writev(s, iov, 2);
133
134      start = 0;
135      while (start < sizeof(response) - 1) {
136        n = read(s, response+start, sizeof(response) - 1 - start);
137        if (n < 1) break;
138        start += n;
139      }
140
141      (void)close(s);
142
143      if (start > 1 && !strncmp(response, "OK", 2)) {
144        return PWCHECK_OK;
145      }
146
147      response[start] = '\0';
148      *reply = response;
149      return PWCHECK_NO;
150  }
151
152 #endif
153
154
155
156  /* A dummy function that always fails if saslauthd support is not
157 wanted. */
158
159 #ifndef CYRUS_SASLAUTHD_SOCKET
160 int saslauthd_verify_password(const uschar *userid,
161                 const uschar *passwd,
162                 const uschar *service,
163                 const uschar *realm,
164                 const uschar **reply)
165 {
166 *reply = US"saslauthd support is not included in this Exim binary";
167 return PWCHECK_FAIL;
168 }
169
170
171 /* This is the real function */
172
173 #else
174  /* written from scratch  */
175  /* saslauthd daemon-authenticated login */
176
177 int saslauthd_verify_password(const uschar *userid,
178                 const uschar *password,
179                 const uschar *service,
180                 const uschar *realm,
181                 const uschar **reply)
182 {
183     uschar *daemon_reply = NULL;
184     int s, r;
185     struct sockaddr_un srvaddr;
186
187     DEBUG(D_auth)
188        debug_printf("saslauthd userid='%s' servicename='%s'"
189                     " realm='%s'\n", userid, service, realm );
190
191     *reply = NULL;
192
193     s = socket(AF_UNIX, SOCK_STREAM, 0);
194     if (s == -1) {
195        *reply = CUstrerror(errno);
196        return PWCHECK_FAIL;
197     }
198
199     memset(CS &srvaddr, 0, sizeof(srvaddr));
200     srvaddr.sun_family = AF_UNIX;
201     strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET,
202             sizeof(srvaddr.sun_path));
203     r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
204     if (r == -1) {
205        DEBUG(D_auth)
206             debug_printf("Cannot connect to saslauthd daemon (at '%s'): %s\n",
207                          CYRUS_SASLAUTHD_SOCKET, strerror(errno));
208        *reply = string_sprintf("cannot connect to saslauthd daemon at "
209                                "%s: %s", CYRUS_SASLAUTHD_SOCKET,
210                                strerror(errno));
211        return PWCHECK_FAIL;
212     }
213
214     if ( write_string(s, userid, Ustrlen(userid)) < 0) {
215         DEBUG(D_auth)
216             debug_printf("Failed to send userid to saslauthd daemon \n");
217         (void)close(s);
218         return PWCHECK_FAIL;
219     }
220
221     if ( write_string(s, password, Ustrlen(password)) < 0) {
222         DEBUG(D_auth)
223             debug_printf("Failed to send password to saslauthd daemon \n");
224         (void)close(s);
225         return PWCHECK_FAIL;
226     }
227
228     memset((void *)password, 0, Ustrlen(password));
229
230     if ( write_string(s, service, Ustrlen(service)) < 0) {
231         DEBUG(D_auth)
232             debug_printf("Failed to send service name to saslauthd daemon \n");
233         (void)close(s);
234         return PWCHECK_FAIL;
235     }
236
237     if ( write_string(s, realm, Ustrlen(realm)) < 0) {
238         DEBUG(D_auth)
239             debug_printf("Failed to send realm to saslauthd daemon \n");
240         (void)close(s);
241         return PWCHECK_FAIL;
242     }
243
244     if ( read_string(s, &daemon_reply ) < 2) {
245         DEBUG(D_auth)
246             debug_printf("Corrupted answer '%s' received. \n", daemon_reply);
247         (void)close(s);
248         return PWCHECK_FAIL;
249     }
250
251     (void)close(s);
252
253     DEBUG(D_auth)
254         debug_printf("Answer '%s' received. \n", daemon_reply);
255
256     *reply = daemon_reply;
257
258     if ( (daemon_reply[0] == 'O') && (daemon_reply[1] == 'K') )
259         return PWCHECK_OK;
260
261     if ( (daemon_reply[0] == 'N') && (daemon_reply[1] == 'O') )
262         return PWCHECK_NO;
263
264     return PWCHECK_FAIL;
265 }
266
267 #endif
268
269
270 /* helper functions */
271 #if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
272
273 #define MAX_REQ_LEN 1024
274
275 /* written from scratch */
276
277 /* FUNCTION: read_string */
278
279 /* SYNOPSIS
280  * read a sasld-style counted string into
281  * store-allocated buffer, set pointer to the buffer,
282  * return number of bytes read or -1 on failure.
283  * END SYNOPSIS */
284
285 static int read_string(int fd, uschar **retval) {
286     unsigned short count;
287     int rc;
288
289     rc = (retry_read(fd, &count, sizeof(count)) < (int) sizeof(count));
290     if (!rc) {
291         count = ntohs(count);
292         if (count > MAX_REQ_LEN) {
293             return -1;
294         } else {
295             /* Assume the file is trusted, so no tainting */
296             *retval = store_get(count + 1, GET_UNTAINTED);
297             rc = (retry_read(fd, *retval, count) < (int) count);
298             (*retval)[count] = '\0';
299             return count;
300         }
301     }
302     return -1;
303 }
304
305
306 /* FUNCTION: write_string */
307
308 /* SYNOPSIS
309  * write a sasld-style counted string into given fd
310  * written bytes on success, -1 on failure.
311  * END SYNOPSIS */
312
313 static int write_string(int fd, const uschar *string, int len) {
314     unsigned short count;
315     int rc;
316     struct iovec iov[2];
317
318     count = htons(len);
319
320     iov[0].iov_base = (void *) &count;
321     iov[0].iov_len = sizeof(count);
322     iov[1].iov_base = (void *) string;
323     iov[1].iov_len = len;
324
325     rc = retry_writev(fd, iov, 2);
326
327     return rc;
328 }
329
330
331 /* taken from cyrus-sasl file saslauthd/saslauthd-unix.c  */
332
333 /* FUNCTION: retry_read */
334
335 /* SYNOPSIS
336  * Keep calling the read() system call with 'fd', 'buf', and 'nbyte'
337  * until all the data is read in or an error occurs.
338  * END SYNOPSIS */
339 static int retry_read(int fd, void *inbuf, unsigned nbyte)
340 {
341     int n;
342     int nread = 0;
343     char *buf = CS inbuf;
344
345     if (nbyte == 0) return 0;
346
347     for (;;) {
348        n = read(fd, buf, nbyte);
349        if (n == 0) {
350            /* end of file */
351            return -1;
352        }
353        if (n == -1) {
354            if (errno == EINTR) continue;
355            return -1;
356        }
357
358        nread += n;
359
360        if (n >= (int) nbyte) return nread;
361
362        buf += n;
363        nbyte -= n;
364     }
365 }
366
367 /* END FUNCTION: retry_read */
368
369 /* FUNCTION: retry_writev */
370
371 /* SYNOPSIS
372  * Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt'
373  * until all the data is written out or an error occurs.
374  * END SYNOPSIS */
375
376 static int     /* R: bytes written, or -1 on error */
377 retry_writev (
378   /* PARAMETERS */
379   int fd,                              /* I: fd to write on */
380   struct iovec *iov,                   /* U: iovec array base
381                                         *    modified as data written */
382   int iovcnt                           /* I: number of iovec entries */
383   /* END PARAMETERS */
384   )
385 {
386     /* VARIABLES */
387     int n;                             /* return value from writev() */
388     int i;                             /* loop counter */
389     int written;                       /* bytes written so far */
390     static int iov_max;                        /* max number of iovec entries */
391     /* END VARIABLES */
392
393     /* initialization */
394 #ifdef MAXIOV
395     iov_max = MAXIOV;
396 #else /* ! MAXIOV */
397 # ifdef IOV_MAX
398     iov_max = IOV_MAX;
399 # else /* ! IOV_MAX */
400     iov_max = 8192;
401 # endif /* ! IOV_MAX */
402 #endif /* ! MAXIOV */
403     written = 0;
404
405     for (;;) {
406
407        while (iovcnt && iov[0].iov_len == 0) {
408            iov++;
409            iovcnt--;
410        }
411
412        if (!iovcnt) {
413            return written;
414        }
415
416        n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
417        if (n == -1) {
418            if (errno == EINVAL && iov_max > 10) {
419                iov_max /= 2;
420                continue;
421            }
422            if (errno == EINTR) {
423                continue;
424            }
425            return -1;
426        } else {
427            written += n;
428        }
429
430        for (i = 0; i < iovcnt; i++) {
431            if (iov[i].iov_len > (unsigned) n) {
432                iov[i].iov_base = CS iov[i].iov_base + n;
433                iov[i].iov_len -= n;
434                break;
435            }
436            n -= iov[i].iov_len;
437            iov[i].iov_len = 0;
438        }
439
440        if (i == iovcnt) {
441            return written;
442        }
443     }
444     /* NOTREACHED */
445 }
446
447 /* END FUNCTION: retry_writev */
448 #endif
449
450 /* End of auths/pwcheck.c */