]> git.ozlabs.org Git - ppp.git/blob - pppd/session.c
Nit: use _exit when exec fails and restrict values to 0-255 per POSIX.
[ppp.git] / pppd / session.c
1 /*
2  * session.c - PPP session control.
3  *
4  * Copyright (c) 2007 Diego Rivera. 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  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. The name(s) of the authors of this software must not be used to
14  *    endorse or promote products derived from this software without
15  *    prior written permission.
16  *
17  * 3. Redistributions of any form whatsoever must retain the following
18  *    acknowledgment:
19  *    "This product includes software developed by Paul Mackerras
20  *     <paulus@samba.org>".
21  *
22  * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
23  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
24  * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
25  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
26  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
27  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
28  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
29  *
30  * Derived from auth.c, which is:
31  *
32  * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
33  *
34  * Redistribution and use in source and binary forms, with or without
35  * modification, are permitted provided that the following conditions
36  * are met:
37  *
38  * 1. Redistributions of source code must retain the above copyright
39  *    notice, this list of conditions and the following disclaimer.
40  *
41  * 2. Redistributions in binary form must reproduce the above copyright
42  *    notice, this list of conditions and the following disclaimer in
43  *    the documentation and/or other materials provided with the
44  *    distribution.
45  *
46  * 3. The name "Carnegie Mellon University" must not be used to
47  *    endorse or promote products derived from this software without
48  *    prior written permission. For permission or any legal
49  *    details, please contact
50  *      Office of Technology Transfer
51  *      Carnegie Mellon University
52  *      5000 Forbes Avenue
53  *      Pittsburgh, PA  15213-3890
54  *      (412) 268-4387, fax: (412) 268-7395
55  *      tech-transfer@andrew.cmu.edu
56  *
57  * 4. Redistributions of any form whatsoever must retain the following
58  *    acknowledgment:
59  *    "This product includes software developed by Computing Services
60  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
61  *
62  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
63  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
64  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
65  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
66  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
67  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
68  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
69  */
70
71 #include <stdio.h>
72 #include <stdlib.h>
73 #include <string.h>
74 #include <pwd.h>
75 #include <crypt.h>
76 #ifdef HAS_SHADOW
77 #include <shadow.h>
78 #endif
79 #include <time.h>
80 #include <utmp.h>
81 #include <fcntl.h>
82 #include <unistd.h>
83 #include "pppd.h"
84 #include "session.h"
85
86 #ifdef USE_PAM
87 #include <security/pam_appl.h>
88 #endif /* #ifdef USE_PAM */
89
90 #define SET_MSG(var, msg) if (var != NULL) { var[0] = msg; }
91 #define COPY_STRING(s) ((s) ? strdup(s) : NULL)
92
93 #define SUCCESS_MSG "Session started successfully"
94 #define ABORT_MSG "Session can't be started without a username"
95 #define SERVICE_NAME "ppp"
96
97 #define SESSION_FAILED  0
98 #define SESSION_OK      1
99
100 /* We have successfully started a session */
101 static bool logged_in = 0;
102
103 #ifdef USE_PAM
104 /*
105  * Static variables used to communicate between the conversation function
106  * and the server_login function
107  */
108 static const char *PAM_username;
109 static const char *PAM_password;
110 static int   PAM_session = 0;
111 static pam_handle_t *pamh = NULL;
112
113 /* PAM conversation function
114  * Here we assume (for now, at least) that echo on means login name, and
115  * echo off means password.
116  */
117
118 static int conversation (int num_msg,
119 #ifndef SOL2
120     const
121 #endif
122     struct pam_message **msg,
123     struct pam_response **resp, void *appdata_ptr)
124 {
125     int replies = 0;
126     struct pam_response *reply = NULL;
127
128     reply = malloc(sizeof(struct pam_response) * num_msg);
129     if (!reply) return PAM_CONV_ERR;
130
131     for (replies = 0; replies < num_msg; replies++) {
132         switch (msg[replies]->msg_style) {
133             case PAM_PROMPT_ECHO_ON:
134                 reply[replies].resp_retcode = PAM_SUCCESS;
135                 reply[replies].resp = COPY_STRING(PAM_username);
136                 /* PAM frees resp */
137                 break;
138             case PAM_PROMPT_ECHO_OFF:
139                 reply[replies].resp_retcode = PAM_SUCCESS;
140                 reply[replies].resp = COPY_STRING(PAM_password);
141                 /* PAM frees resp */
142                 break;
143             case PAM_TEXT_INFO:
144                 /* fall through */
145             case PAM_ERROR_MSG:
146                 /* ignore it, but pam still wants a NULL response... */
147                 reply[replies].resp_retcode = PAM_SUCCESS;
148                 reply[replies].resp = NULL;
149                 break;
150             default:
151                 /* Must be an error of some sort... */
152                 free (reply);
153                 return PAM_CONV_ERR;
154         }
155     }
156     *resp = reply;
157     return PAM_SUCCESS;
158 }
159
160 static struct pam_conv pam_conv_data = {
161     &conversation,
162     NULL
163 };
164 #endif /* #ifdef USE_PAM */
165
166 int
167 session_start(flags, user, passwd, ttyName, msg)
168     const int flags;
169     const char *user;
170     const char *passwd;
171     const char *ttyName;
172     char **msg;
173 {
174 #ifdef USE_PAM
175     bool ok = 1;
176     const char *usr;
177     int pam_error;
178     bool try_session = 0;
179 #else /* #ifdef USE_PAM */
180     struct passwd *pw;
181 #ifdef HAS_SHADOW
182     struct spwd *spwd;
183     struct spwd *getspnam();
184     long now = 0;
185 #endif /* #ifdef HAS_SHADOW */
186 #endif /* #ifdef USE_PAM */
187
188     SET_MSG(msg, SUCCESS_MSG);
189
190     /* If no verification is requested, then simply return an OK */
191     if (!(SESS_ALL & flags)) {
192         return SESSION_OK;
193     }
194
195     if (user == NULL) {
196        SET_MSG(msg, ABORT_MSG);
197        return SESSION_FAILED;
198     }
199
200 #ifdef USE_PAM
201     /* Find the '\\' in the username */
202     /* This needs to be fixed to support different username schemes */
203     if ((usr = strchr(user, '\\')) == NULL)
204         usr = user;
205     else
206         usr++;
207
208     PAM_session = 0;
209     PAM_username = usr;
210     PAM_password = passwd;
211
212     dbglog("Initializing PAM (%d) for user %s", flags, usr);
213     pam_error = pam_start (SERVICE_NAME, usr, &pam_conv_data, &pamh);
214     dbglog("---> PAM INIT Result = %d", pam_error);
215     ok = (pam_error == PAM_SUCCESS);
216
217     if (ok) {
218         ok = (pam_set_item(pamh, PAM_TTY, ttyName) == PAM_SUCCESS) &&
219             (pam_set_item(pamh, PAM_RHOST, ifname) == PAM_SUCCESS);
220     }
221
222     if (ok && (SESS_AUTH & flags)) {
223         dbglog("Attempting PAM authentication");
224         pam_error = pam_authenticate (pamh, PAM_SILENT);
225         if (pam_error == PAM_SUCCESS) {
226             /* PAM auth was OK */
227             dbglog("PAM Authentication OK for %s", user);
228         } else {
229             /* No matter the reason, we fail because we're authenticating */
230             ok = 0;
231             if (pam_error == PAM_USER_UNKNOWN) {
232                 dbglog("User unknown, failing PAM authentication");
233                 SET_MSG(msg, "User unknown - cannot authenticate via PAM");
234             } else {
235                 /* Any other error means authentication was bad */
236                 dbglog("PAM Authentication failed: %d: %s", pam_error,
237                        pam_strerror(pamh, pam_error));
238                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
239             }
240         }
241     }
242
243     if (ok && (SESS_ACCT & flags)) {
244         dbglog("Attempting PAM account checks");
245         pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
246         if (pam_error == PAM_SUCCESS) {
247             /*
248              * PAM account was OK, set the flag which indicates that we should
249              * try to perform the session checks.
250              */
251             try_session = 1;
252             dbglog("PAM Account OK for %s", user);
253         } else {
254             /*
255              * If the account checks fail, then we should not try to perform
256              * the session check, because they don't make sense.
257              */
258             try_session = 0;
259             if (pam_error == PAM_USER_UNKNOWN) {
260                 /*
261                  * We're checking the account, so it's ok to not have one
262                  * because the user might come from the secrets files, or some
263                  * other plugin.
264                  */
265                 dbglog("User unknown, ignoring PAM restrictions");
266                 SET_MSG(msg, "User unknown - ignoring PAM restrictions");
267             } else {
268                 /* Any other error means session is rejected */
269                 ok = 0;
270                 dbglog("PAM Account checks failed: %d: %s", pam_error,
271                        pam_strerror(pamh, pam_error));
272                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
273             }
274         }
275     }
276
277     if (ok && try_session && (SESS_ACCT & flags)) {
278         /* Only open a session if the user's account was found */
279         pam_error = pam_open_session (pamh, PAM_SILENT);
280         if (pam_error == PAM_SUCCESS) {
281             dbglog("PAM Session opened for user %s", user);
282             PAM_session = 1;
283         } else {
284             dbglog("PAM Session denied for user %s", user);
285             SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
286             ok = 0;
287         }
288     }
289
290     /* This is needed because apparently the PAM stuff closes the log */
291     reopen_log();
292
293     /* If our PAM checks have already failed, then we must return a failure */
294     if (!ok) return SESSION_FAILED;
295
296 #else /* #ifdef USE_PAM */
297
298 /*
299  * Use the non-PAM methods directly.  'pw' will remain NULL if the user
300  * has not been authenticated using local UNIX system services.
301  */
302
303     pw = NULL;
304     if ((SESS_AUTH & flags)) {
305         pw = getpwnam(user);
306
307         endpwent();
308         /*
309          * Here, we bail if we have no user account, because there is nothing
310          * to verify against.
311          */
312         if (pw == NULL)
313             return SESSION_FAILED;
314
315 #ifdef HAS_SHADOW
316
317         spwd = getspnam(user);
318         endspent();
319
320         /*
321          * If there is no shadow entry for the user, then we can't verify the
322          * account.
323          */
324         if (spwd == NULL)
325             return SESSION_FAILED;
326
327         /*
328          * We check validity all the time, because if the password has expired,
329          * then clearly we should not authenticate against it (if we're being
330          * called for authentication only).  Thus, in this particular instance,
331          * there is no real difference between using the AUTH, SESS or ACCT
332          * flags, or combinations thereof.
333          */
334         now = time(NULL) / 86400L;
335         if ((spwd->sp_expire > 0 && now >= spwd->sp_expire)
336             || ((spwd->sp_max >= 0 && spwd->sp_max < 10000)
337             && spwd->sp_lstchg >= 0
338             && now >= spwd->sp_lstchg + spwd->sp_max)) {
339             warn("Password for %s has expired", user);
340             return SESSION_FAILED;
341         }
342
343         /* We have a valid shadow entry, keep the password */
344         pw->pw_passwd = spwd->sp_pwdp;
345
346 #endif /* #ifdef HAS_SHADOW */
347
348         /*
349          * If no passwd, don't let them login if we're authenticating.
350          */
351         if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2
352             || strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd) != 0)
353             return SESSION_FAILED;
354     }
355
356 #endif /* #ifdef USE_PAM */
357
358     /*
359      * Write a wtmp entry for this user.
360      */
361
362     if (SESS_ACCT & flags) {
363         if (strncmp(ttyName, "/dev/", 5) == 0)
364             ttyName += 5;
365         logwtmp(ttyName, user, ifname); /* Add wtmp login entry */
366         logged_in = 1;
367
368 #if defined(_PATH_LASTLOG) && !defined(USE_PAM)
369         /*
370          * Enter the user in lastlog only if he has been authenticated using
371          * local system services.  If he has not, then we don't know what his
372          * UID might be, and lastlog is indexed by UID.
373          */
374         if (pw != NULL) {
375             struct lastlog ll;
376             int fd;
377             time_t tnow;
378
379             if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) {
380                 (void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET);
381                 memset((void *)&ll, 0, sizeof(ll));
382                 (void)time(&tnow);
383                 ll.ll_time = tnow;
384                 (void)strncpy(ll.ll_line, ttyName, sizeof(ll.ll_line));
385                 (void)strncpy(ll.ll_host, ifname, sizeof(ll.ll_host));
386                 (void)write(fd, (char *)&ll, sizeof(ll));
387                 (void)close(fd);
388             }
389         }
390 #endif /* _PATH_LASTLOG and not USE_PAM */
391         info("user %s logged in on tty %s intf %s", user, ttyName, ifname);
392     }
393
394     return SESSION_OK;
395 }
396
397 /*
398  * session_end - Logout the user.
399  */
400 void
401 session_end(const char* ttyName)
402 {
403 #ifdef USE_PAM
404     int pam_error = PAM_SUCCESS;
405
406     if (pamh != NULL) {
407         if (PAM_session) pam_error = pam_close_session (pamh, PAM_SILENT);
408         PAM_session = 0;
409         pam_end (pamh, pam_error);
410         pamh = NULL;
411         /* Apparently the pam stuff does closelog(). */
412         reopen_log();
413     }
414 #endif
415     if (logged_in) {
416         if (strncmp(ttyName, "/dev/", 5) == 0)
417             ttyName += 5;
418         logwtmp(ttyName, "", ""); /* Wipe out utmp logout entry */
419         logged_in = 0;
420     }
421 }