1 /*
2  * Copyright (c) 2001, 2002, 2004, 2005, 2006, 2008, 2009, 2010, 2011, 2012,
3  *               2013
4  *      Inferno Nettverk A/S, Norway.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. The above copyright notice, this list of conditions and the following
10  *    disclaimer must appear in all copies of the software, derivative works
11  *    or modified versions, and any portions thereof, aswell as in all
12  *    supporting documentation.
13  * 2. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by
16  *      Inferno Nettverk A/S, Norway.
17  * 3. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * Inferno Nettverk A/S requests users of this software to return to
32  *
33  *  Software Distribution Coordinator  or  sdc@inet.no
34  *  Inferno Nettverk A/S
35  *  Oslo Research Park
36  *  Gaustadall�en 21
37  *  NO-0349 Oslo
38  *  Norway
39  *
40  * any improvements or extensions that they make and grant Inferno Nettverk A/S
41  * the rights to redistribute these changes.
42  *
43  */
44 
45 /*
46  * Based on code originally from
47  * Patrick Bihan-Faou, MindStep Corporation, patrick@mindstep.com.
48  */
49 
50 #include "common.h"
51 
52 #if HAVE_PAM
53 
54 static const char rcsid[] =
55 "$Id: auth_pam.c,v 1.100 2013/10/27 15:24:42 karls Exp $";
56 
57 static int
58 pam_conversation(int msgc, const struct pam_message **msgv,
59                  struct pam_response **rspv, void *authdata);
60 /*
61  * Called by the pam system to fetch username and password info.
62  */
63 
64 
65 int
pam_passwordcheck(s,src,dst,auth,emsg,emsgsize)66 pam_passwordcheck(s, src, dst, auth, emsg, emsgsize)
67    int s;
68    const struct sockaddr_storage *src, *dst;
69    const authmethod_pam_t *auth;
70    char *emsg;
71    size_t emsgsize;
72 {
73    const char *function = "pam_passwordcheck()";
74    authmethod_pam_t authdata = *auth;
75    struct pam_conv pamconv;
76    pam_handle_t *pamh;
77    size_t i;
78    char srcstr[MAXSOCKADDRSTRING], visbuf[MAXNAMELEN * 4];
79    int rc;
80 
81    /*
82     * unfortunately we can not set password here, that needs to be set
83     * "from a module", i.e. in the conversion function, at least with
84     * one Linux pam implementation.
85     */
86    struct {
87       int         item;
88       const char  *itemname;
89       const void  *value;
90       int         printable;
91 
92    } pamval[] = {
93       { (int)PAM_CONV,  "PAM_CONV",  &pamconv,                              0 },
94       { (int)PAM_RHOST, "PAM_RHOST",
95                   src == NULL ?
96                      "" : sockaddr2string2(src, 0, srcstr, sizeof(srcstr)), 1 },
97       { (int)PAM_USER,  "PAM_USER",  (*auth->name == NUL) ?
98                               DEFAULT_PAM_USER : (const char *)auth->name,  1 },
99       { (int)PAM_RUSER, "PAM_RUSER", DEFAULT_PAM_RUSER,                     1 },
100    };
101 
102    slog(LOG_DEBUG, "%s: src %s, user \"%s\", servicename \"%s\", emsgsize %ld",
103         function,
104         src == NULL ? "N/A" : sockaddr2string(src, NULL, 0),
105         str2vis((const char *)auth->name,
106                 strlen((const char *)auth->name),
107                 visbuf,
108                 sizeof(visbuf)),
109         auth->servicename,
110         (long)emsgsize);
111 
112    if (src == NULL) {
113       snprintf(emsg, emsgsize, "%s: NULL src address: not supported", function);
114       return  -1;
115    }
116    /*
117     * Note: we can not save the state of pam after pam_start(3), as
118     * e.g. Solaris 5.11 pam does not allow setting PAM_SERVICE
119     * except during pam_start(3), while we may need to change it
120     * depending on the client/rule.
121     * Some Linux pam-implementations on the other hand can enter
122     * some sort of busy-loop if we don't call pam_end(3) ever so
123     * often.
124     *
125     * Therefor, disregard all possible optimization stuff for now and
126     * call pam_start(3) and pam_end(3) every time.
127     */
128 
129    pamconv.conv        = pam_conversation;
130    pamconv.appdata_ptr = &authdata;
131 
132    sockd_priv(SOCKD_PRIV_PAM, PRIV_ON);
133    rc = pam_start(auth->servicename, NULL, &pamconv, &pamh);
134    sockd_priv(SOCKD_PRIV_PAM, PRIV_OFF);
135 
136    if (rc != (int)PAM_SUCCESS) {
137       snprintf(emsg, emsgsize, "pam_start() failed: %s",
138                pam_strerror(pamh, rc));
139 
140       return -1;
141    }
142 
143    for (i = 0; i < ELEMENTS(pamval); ++i) {
144       if (pamval[i].printable) {
145          str2vis((const char *)pamval[i].value,
146                  strlen((const char *)pamval[i].value),
147                  visbuf,
148                  sizeof(visbuf));
149 
150          slog(LOG_DEBUG, "%s: setting item \"%s\" to value \"%s\"",
151               function, pamval[i].itemname, visbuf);
152       }
153       else
154          slog(LOG_DEBUG, "%s: setting item %s", function, pamval[i].itemname);
155 
156       if ((rc = pam_set_item(pamh, pamval[i].item, pamval[i].value))
157       != (int)PAM_SUCCESS) {
158          snprintf(emsg, emsgsize, "pam_set_item(%s) to \"%s\" failed: %s",
159                   pamval[i].itemname, visbuf, pam_strerror(pamh, rc));
160 
161          pam_end(pamh, rc);
162          return -1;
163       }
164    }
165 
166    sockd_priv(SOCKD_PRIV_PAM, PRIV_ON);
167 
168    if ((rc = pam_authenticate(pamh, 0)) != (int)PAM_SUCCESS) {
169       sockd_priv(SOCKD_PRIV_PAM, PRIV_OFF);
170 
171       slog(LOG_DEBUG, "%s: pam_authenticate() failed: %s",
172            function, pam_strerror(pamh, rc));
173 
174       snprintf(emsg, emsgsize, "pam_authenticate() for user \"%s\" failed: %s",
175                *auth->name == NUL ?
176                   "<no user specified>" : str2vis((const char *)auth->name,
177                                                strlen((const char *)auth->name),
178                                                   visbuf,
179                                                   sizeof(visbuf)),
180                pam_strerror(pamh, rc));
181 
182       pam_end(pamh, rc);
183       return -1;
184    }
185 
186    /* LINTED passing const, expecting non-const (PAM_SILENT) */
187    rc = pam_acct_mgmt(pamh, PAM_SILENT);
188 
189    sockd_priv(SOCKD_PRIV_PAM, PRIV_OFF);
190 
191    if (rc != PAM_SUCCESS) {
192       slog(LOG_DEBUG, "%s: pam_acct_mgmt() failed: %s",
193            function, pam_strerror(pamh, rc));
194 
195       snprintf(emsg, emsgsize, "pam_acct_mgmt(): %s", pam_strerror(pamh, rc));
196 
197       pam_end(pamh, rc);
198       return -1;
199    }
200 
201    if ((rc = pam_end(pamh, rc)) != (int)PAM_SUCCESS)
202       swarnx("%s: strange ... pam_end() failed: %s",
203              function, pam_strerror(pamh, rc));
204 
205    slog(LOG_DEBUG, "%s: pam authentication succeeded", function);
206    return 0;
207 }
208 
209 static int
pam_conversation(msgc,msgv,rspv,authdata)210 pam_conversation(msgc, msgv, rspv, authdata)
211    int msgc;
212    const struct pam_message **msgv;
213    struct pam_response **rspv;
214    void *authdata;
215 {
216    const authmethod_pam_t *auth = authdata;
217    const char *function = "pam_conversation()";
218    int i, rc;
219 
220    if (rspv == NULL || msgv == NULL || auth == NULL || msgc < 1) {
221       swarnx("%s: called with invalid/unexpected input", function);
222       return (int)PAM_CONV_ERR;
223    }
224 
225    if (((*rspv) = malloc(msgc * sizeof(struct pam_response))) == NULL) {
226       swarn("%s: malloc(%d * %lu)",
227             function, msgc, (unsigned long)sizeof(struct pam_response));
228 
229       return (int)PAM_CONV_ERR;
230    }
231 
232    /* initialize all to NULL so we can easily free on error. */
233    for (i = 0; i < msgc; ++i) {
234       (*rspv)[i].resp_retcode = 0; /* according to sun not used, should be 0. */
235       (*rspv)[i].resp         = NULL;
236    }
237 
238    rc = (int)PAM_SUCCESS;
239    for (i = 0; i < msgc; ++i) {
240       slog(LOG_DEBUG, "%s: msg_style = %d", function, msgv[i]->msg_style);
241 
242       switch(msgv[i]->msg_style) {
243          case PAM_PROMPT_ECHO_OFF:
244             if (((*rspv)[i].resp = strdup((const char *)auth->password))
245             == NULL) {
246                swarn("%s: strdup() of password, length %lu, failed",
247                      function,
248                      (unsigned long)strlen((const char *)auth->password));
249 
250                rc = (int)PAM_CONV_ERR;
251             }
252             break;
253 
254          case PAM_ERROR_MSG:
255             slog(LOG_INFO, "%s: got a pam error msg: %s",
256                  function, msgv[i]->msg);
257             break;
258 
259          case PAM_TEXT_INFO:
260             /*
261              * not expecting this, and where it has been seen (some versions
262              * of FreeBSD), the string has been empty.
263              * Seen it on Linux also.  Don't know what it's for.
264              */
265             slog(LOG_DEBUG, "%s: got unexpected PAM_TEXT_INFO: \"%s\"",
266                  function, msgv[i]->msg);
267             break;
268 
269          default:
270             swarnx("%s: unknown msg_style %d, ignored ...",
271                    function, msgv[i]->msg_style);
272             break;
273       }
274    }
275 
276    if (rc != (int)PAM_SUCCESS) { /* failed; free the memory ourselves */
277       for (i = 0; i < msgc; ++i)
278          free((*rspv)[i].resp);
279 
280       free(*rspv);
281    }
282 
283    return rc;
284 }
285 
286 #endif /* HAVE_PAM */
287