1 /* auth_unix.c -- Unix passwd file authorization
2  *
3  * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 #include <config.h>
44 #include <stdlib.h>
45 #include <pwd.h>
46 #include <grp.h>
47 #include <ctype.h>
48 #include <string.h>
49 
50 #include "auth.h"
51 #include "libcyr_cfg.h"
52 #include "xmalloc.h"
53 #include "strarray.h"
54 #include "util.h"
55 
56 struct auth_state {
57     char userid[81];
58     strarray_t groups;
59 };
60 
61 static struct auth_state auth_anonymous = {
62     "anonymous", STRARRAY_INITIALIZER
63 };
64 
65 /*
66  * Determine if the user is a member of 'identifier'
67  * Returns one of:
68  *      0       User does not match identifier
69  *      1       identifier matches everybody
70  *      2       User is in the group that is identifier
71  *      3       User is identifer
72  */
mymemberof(const struct auth_state * auth_state,const char * identifier)73 static int mymemberof(const struct auth_state *auth_state, const char *identifier)
74 {
75     int i;
76 
77     if (!auth_state) auth_state = &auth_anonymous;
78 
79     if (strcmp(identifier, "anyone") == 0) return 1;
80 
81     if (strcmp(identifier, auth_state->userid) == 0) return 3;
82 
83     if (strncmp(identifier, "group:", 6) != 0) return 0;
84 
85     for (i=0; i<auth_state->groups.count ; i++) {
86         if (strcmp(identifier+6, auth_state->groups.data[i]) == 0) return 2;
87     }
88     return 0;
89 }
90 
91 /* Map of which characters are allowed by auth_canonifyid.
92  * Key: 0 -> not allowed (special, ctrl, or would confuse Unix or imapd)
93  *      1 -> allowed, but requires an alpha somewhere else in the string
94  *      2 -> allowed, and is an alpha
95  *
96  * At least one character must be an alpha.
97  *
98  * This may not be restrictive enough.
99  * Here are the reasons for the restrictions:
100  *
101  * &    forbidden because of MUTF-7.  (This could be fixed.)
102  * :    forbidden because it's special in /etc/passwd
103  * /    forbidden because it can't be used in a mailbox name
104  * * %  forbidden because they're IMAP magic in the LIST/LSUB commands
105  * ?    it just scares me
106  * ctrl chars, DEL
107  *      can't send them as IMAP characters in plain folder names, I think
108  * 80-FF forbidden because you can't send them in IMAP anyway
109  *       (and they're forbidden as folder names). (This could be fixed.)
110  *
111  * + and - are *allowed* although '+' is probably used for userid+detail
112  * subaddressing and qmail users use '-' for subaddressing.
113  *
114  * Identifiers don't require a digit, really, so that should probably be
115  * relaxed, too.
116  */
117 static char allowedchars[256] = {
118  /* 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
119     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00-0F */
120     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10-1F */
121     1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* 20-2F */
122     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* 30-3F */
123 
124     1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 40-4F */
125     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, /* 50-5F */
126     1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 60-6F */
127     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, /* 70-7F */
128 
129     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
130     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
131     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
132     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
133 
134     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
135     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
136     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
137     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
138 };
139 
140 /*
141  * Convert 'identifier' into canonical form.
142  * Returns a pointer to a static buffer containing the canonical form
143  * or NULL if 'identifier' is invalid.
144  *
145  * XXX If any of the characters marked with 0 are valid and are cropping up,
146  * the right thing to do is probably to canonicalize the identifier to two
147  * representations: one for getpwent calls and one for folder names.  The
148  * latter canonicalizes to a MUTF7 representation.
149  */
mycanonifyid(const char * identifier,size_t len)150 static const char *mycanonifyid(const char *identifier, size_t len)
151 {
152     static char retbuf[81];
153     struct group *grp;
154     char *p;
155     int username_tolower = 0;
156 
157     if (!len) len = strlen(identifier);
158     if (len >= sizeof(retbuf)) return NULL;
159 
160     memmove(retbuf, identifier, len);
161     retbuf[len] = '\0';
162 
163     /* This used to be far more restrictive, but many sites seem to ignore the
164      * ye olde Unix conventions of username.  Specifically, we used to
165      * - drop case on the buffer
166      * - disallow lots of non-alpha characters ('-', '_', others)
167      * Now we do neither of these, but impose a very different policy based on
168      * the character map above.
169      */
170 
171     if (!strncmp(retbuf, "group:", 6)) {
172         grp = getgrnam(retbuf+6);
173         if (!grp) return NULL;
174         if (strlen(grp->gr_name) >= sizeof(retbuf)-6)
175                 return NULL;
176         strcpy(retbuf+6, grp->gr_name);
177         return retbuf;
178     }
179 
180     /* Copy the string and look up values in the allowedchars array above.
181      * If we see any we don't like, reject the string.
182      * Lowercase usernames if requested.
183      */
184     username_tolower = libcyrus_config_getswitch(CYRUSOPT_USERNAME_TOLOWER);
185     for (p = retbuf; *p; p++) {
186         if (username_tolower && Uisupper(*p))
187             *p = tolower((unsigned char)*p);
188 
189         switch (allowedchars[*(unsigned char*) p]) {
190         case 0:
191             return NULL;
192         default:
193             break;
194         }
195     }
196 
197     return retbuf;
198 }
199 
200 /*
201  * Set the current user to 'identifier'.  'cacheid', if non-null,
202  * points to a 16-byte binary key to cache identifier's information
203  * with.
204  */
mynewstate(const char * identifier)205 static struct auth_state *mynewstate(const char *identifier)
206 {
207     struct auth_state *newstate;
208     struct passwd *pwd;
209     struct group *grp;
210 #if defined(HAVE_GETGROUPLIST) && defined(__GLIBC__)
211     gid_t gid, *groupids = NULL;
212     int ret, ngroups = 10, oldngroups;
213 #else
214     char **mem;
215 #endif
216 
217     identifier = mycanonifyid(identifier, 0);
218     if (!identifier) return 0;
219     if (!strncmp(identifier, "group:", 6)) return 0;
220 
221     newstate = (struct auth_state *)xmalloc(sizeof(struct auth_state));
222 
223     strcpy(newstate->userid, identifier);
224     strarray_init(&newstate->groups);
225 
226     if(!libcyrus_config_getswitch(CYRUSOPT_AUTH_UNIX_GROUP_ENABLE))
227         return newstate;
228 
229     pwd = getpwnam(identifier);
230 
231 #if defined(HAVE_GETGROUPLIST) && defined(__GLIBC__)
232     gid = pwd ? pwd->pw_gid : (gid_t) -1;
233 
234     /* get the group ids */
235     do {
236         groupids = (gid_t *)xrealloc((gid_t *)groupids,
237                                      ngroups * sizeof(gid_t));
238 
239         oldngroups = ngroups; /* copy of ngroups for comparison */
240         ret = getgrouplist(identifier, gid, groupids, &ngroups);
241         /*
242          * This is tricky. We do this as long as getgrouplist tells us to
243          * realloc _and_ the number of groups changes. It tells us to realloc
244          * also in the case of failure...
245          */
246     } while (ret == -1 && ngroups != oldngroups);
247 
248     if (ret == -1)
249         goto err;
250 
251     while (ngroups--) {
252         if (pwd || groupids[ngroups] != gid) {
253             if ((grp = getgrgid(groupids[ngroups])))
254                 strarray_append(&newstate->groups, grp->gr_name);
255         }
256     }
257 
258 err:
259     if (groupids) free(groupids);
260 
261 #else /* !HAVE_GETGROUPLIST */
262     setgrent();
263     while ((grp = getgrent())) {
264         for (mem = grp->gr_mem; *mem; mem++) {
265             if (!strcmp(*mem, identifier)) break;
266         }
267 
268         if (*mem || (pwd && pwd->pw_gid == grp->gr_gid))
269             strarray_append(&newstate->groups, grp->gr_name);
270     }
271     endgrent();
272 #endif /* HAVE_GETGROUPLIST */
273 
274     return newstate;
275 }
276 
myfreestate(struct auth_state * auth_state)277 static void myfreestate(struct auth_state *auth_state)
278 {
279     strarray_fini(&auth_state->groups);
280     free(auth_state);
281 }
282 
mygroups(const struct auth_state * auth_state)283 static strarray_t *mygroups(const struct auth_state *auth_state)
284 {
285     return strarray_dup(&auth_state->groups);
286 }
287 
288 HIDDEN struct auth_mech auth_unix =
289 {
290     "unix",             /* name */
291 
292     &mycanonifyid,
293     &mymemberof,
294     &mynewstate,
295     &myfreestate,
296     &mygroups,
297 };
298