Import Linux-PAM.
[profile/ivi/pam.git] / modules / pam_keyinit / pam_keyinit.c
1 /* pam_keyinit.c: Initialise the session keyring on login through a PAM module
2  *
3  * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved.
4  * Written by David Howells (dhowells@redhat.com)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version
9  * 2 of the License, or (at your option) any later version.
10  */
11
12 #include "config.h"
13 #include <stdarg.h>
14 #include <string.h>
15 #include <syslog.h>
16 #include <pwd.h>
17 #include <unistd.h>
18 #include <errno.h>
19 #include <security/pam_modules.h>
20 #include <security/pam_modutil.h>
21 #include <security/pam_ext.h>
22 #include <sys/syscall.h>
23
24 #define KEY_SPEC_SESSION_KEYRING        -3 /* ID for session keyring */
25 #define KEY_SPEC_USER_KEYRING           -4 /* ID for UID-specific keyring */
26 #define KEY_SPEC_USER_SESSION_KEYRING   -5 /* - key ID for UID-session keyring */
27
28 #define KEYCTL_GET_KEYRING_ID           0 /* ask for a keyring's ID */
29 #define KEYCTL_JOIN_SESSION_KEYRING     1 /* start named session keyring */
30 #define KEYCTL_REVOKE                   3 /* revoke a key */
31 #define KEYCTL_LINK                     8 /* link a key into a keyring */
32
33 static int my_session_keyring;
34 static int session_counter;
35 static int do_revoke;
36 static int revoke_as_uid;
37 static int revoke_as_gid;
38 static int xdebug = 0;
39
40 static void debug(pam_handle_t *pamh, const char *fmt, ...)
41         __attribute__((format(printf, 2, 3)));
42
43 static void debug(pam_handle_t *pamh, const char *fmt, ...)
44 {
45         va_list va;
46
47         if (xdebug) {
48                 va_start(va, fmt);
49                 pam_vsyslog(pamh, LOG_DEBUG, fmt, va);
50                 va_end(va);
51         }
52 }
53
54 static int error(pam_handle_t *pamh, const char *fmt, ...)
55         __attribute__((format(printf, 2, 3)));
56
57 static int error(pam_handle_t *pamh, const char *fmt, ...)
58 {
59         va_list va;
60
61         va_start(va, fmt);
62         pam_vsyslog(pamh, LOG_ERR, fmt, va);
63         va_end(va);
64
65         return PAM_SESSION_ERR;
66 }
67
68 /*
69  * initialise the session keyring for this process
70  */
71 static int init_keyrings(pam_handle_t *pamh, int force)
72 {
73         int session, usession, ret;
74
75         if (!force) {
76                 /* get the IDs of the session keyring and the user session
77                  * keyring */
78                 session = syscall(__NR_keyctl,
79                                   KEYCTL_GET_KEYRING_ID,
80                                   KEY_SPEC_SESSION_KEYRING,
81                                   0);
82                 debug(pamh, "GET SESSION = %d", session);
83                 if (session < 0) {
84                         /* don't worry about keyrings if facility not
85                          * installed */
86                         if (errno == ENOSYS)
87                                 return PAM_SUCCESS;
88                         return PAM_SESSION_ERR;
89                 }
90
91                 usession = syscall(__NR_keyctl,
92                                    KEYCTL_GET_KEYRING_ID,
93                                    KEY_SPEC_USER_SESSION_KEYRING,
94                                    0);
95                 debug(pamh, "GET SESSION = %d", usession);
96                 if (usession < 0)
97                         return PAM_SESSION_ERR;
98
99                 /* if the user session keyring is our keyring, then we don't
100                  * need to do anything if we're not forcing */
101                 if (session != usession)
102                         return PAM_SUCCESS;
103         }
104
105         /* create a session keyring, discarding the old one */
106         ret = syscall(__NR_keyctl,
107                       KEYCTL_JOIN_SESSION_KEYRING,
108                       NULL);
109         debug(pamh, "JOIN = %d", ret);
110         if (ret < 0)
111                 return PAM_SESSION_ERR;
112
113         my_session_keyring = ret;
114
115         /* make a link from the session keyring to the user keyring */
116         ret = syscall(__NR_keyctl,
117                       KEYCTL_LINK,
118                       KEY_SPEC_USER_KEYRING,
119                       KEY_SPEC_SESSION_KEYRING);
120
121         return ret < 0 ? PAM_SESSION_ERR : PAM_SUCCESS;
122 }
123
124 /*
125  * revoke the session keyring for this process
126  */
127 static void kill_keyrings(pam_handle_t *pamh)
128 {
129         int old_uid, old_gid;
130
131         /* revoke the session keyring we created earlier */
132         if (my_session_keyring > 0) {
133                 debug(pamh, "REVOKE %d", my_session_keyring);
134
135                 old_uid = geteuid();
136                 old_gid = getegid();
137                 debug(pamh, "UID:%d [%d]  GID:%d [%d]",
138                       revoke_as_uid, old_uid, revoke_as_gid, old_gid);
139
140                 /* switch to the real UID and GID so that we have permission to
141                  * revoke the key */
142                 if (revoke_as_gid != old_gid && setregid(-1, revoke_as_gid) < 0)
143                         error(pamh, "Unable to change GID to %d temporarily\n",
144                               revoke_as_gid);
145
146                 if (revoke_as_uid != old_uid && setresuid(-1, revoke_as_uid, old_uid) < 0)
147                         error(pamh, "Unable to change UID to %d temporarily\n",
148                               revoke_as_uid);
149
150                 syscall(__NR_keyctl,
151                         KEYCTL_REVOKE,
152                         my_session_keyring);
153
154                 /* return to the orignal UID and GID (probably root) */
155                 if (revoke_as_uid != old_uid && setreuid(-1, old_uid) < 0)
156                         error(pamh, "Unable to change UID back to %d\n", old_uid);
157
158                 if (revoke_as_gid != old_gid && setregid(-1, old_gid) < 0)
159                         error(pamh, "Unable to change GID back to %d\n", old_gid);
160
161                 my_session_keyring = 0;
162         }
163 }
164
165 /*
166  * open a PAM session by making sure there's a session keyring
167  */
168 PAM_EXTERN
169 int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
170                         int argc, const char **argv)
171 {
172         struct passwd *pw;
173         const char *username;
174         int ret, old_uid, uid, old_gid, gid, loop, force = 0;
175
176         for (loop = 0; loop < argc; loop++) {
177                 if (strcmp(argv[loop], "force") == 0)
178                         force = 1;
179                 else if (strcmp(argv[loop], "debug") == 0)
180                         xdebug = 1;
181                 else if (strcmp(argv[loop], "revoke") == 0)
182                         do_revoke = 1;
183         }
184
185         /* don't do anything if already created a keyring (will be called
186          * multiple times if mentioned more than once in a pam script)
187          */
188         session_counter++;
189
190         debug(pamh, "OPEN %d", session_counter);
191
192         if (my_session_keyring > 0)
193                 return PAM_SUCCESS;
194
195         /* look up the target UID */
196         ret = pam_get_user(pamh, &username, "key user");
197         if (ret != PAM_SUCCESS)
198                 return ret;
199
200         pw = pam_modutil_getpwnam(pamh, username);
201         if (!pw) {
202                 error(pamh, "Unable to look up user \"%s\"\n", username);
203                 return PAM_USER_UNKNOWN;
204         }
205
206         revoke_as_uid = uid = pw->pw_uid;
207         old_uid = getuid();
208         revoke_as_gid = gid = pw->pw_gid;
209         old_gid = getgid();
210         debug(pamh, "UID:%d [%d]  GID:%d [%d]", uid, old_uid, gid, old_gid);
211
212         /* switch to the real UID and GID so that the keyring ends up owned by
213          * the right user */
214         if (gid != old_gid && setregid(gid, -1) < 0) {
215                 error(pamh, "Unable to change GID to %d temporarily\n", gid);
216                 return PAM_SESSION_ERR;
217         }
218
219         if (uid != old_uid && setreuid(uid, -1) < 0) {
220                 error(pamh, "Unable to change UID to %d temporarily\n", uid);
221                 setregid(old_gid, -1);
222                 return PAM_SESSION_ERR;
223         }
224
225         ret = init_keyrings(pamh, force);
226
227         /* return to the orignal UID and GID (probably root) */
228         if (uid != old_uid && setreuid(old_uid, -1) < 0)
229                 ret = error(pamh, "Unable to change UID back to %d\n", old_uid);
230
231         if (gid != old_gid && setregid(old_gid, -1) < 0)
232                 ret = error(pamh, "Unable to change GID back to %d\n", old_gid);
233
234         return ret;
235 }
236
237 /*
238  * close a PAM session by revoking the session keyring if requested
239  */
240 PAM_EXTERN
241 int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
242                          int argc UNUSED, const char **argv UNUSED)
243 {
244         debug(pamh, "CLOSE %d,%d,%d",
245               session_counter, my_session_keyring, do_revoke);
246
247         session_counter--;
248
249         if (session_counter == 0 && my_session_keyring > 0 && do_revoke)
250                 kill_keyrings(pamh);
251
252         return PAM_SUCCESS;
253 }
254
255 #ifdef PAM_STATIC
256
257 /* static module data */
258
259 struct pam_module _pam_keyinit_modstruct = {
260      "pam_keyinit",
261      NULL,
262      NULL,
263      NULL,
264      pam_sm_open_session,
265      pam_sm_close_session,
266      NULL
267 };
268 #endif
269