1 /*
2  * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2008,
3  *               2009, 2010, 2011, 2012, 2013, 2019, 2020, 2021
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 #include "common.h"
46 
47 static const char rcsid[] =
48 "$Id: accesscheck.c,v 1.89.10.13 2021/03/24 23:06:06 karls Exp $";
49 
50 int
usermatch(auth,userlist)51 usermatch(auth, userlist)
52    const authmethod_t *auth;
53    const linkedname_t *userlist;
54 {
55 /*   const char *function = "usermatch()"; */
56    const char *name;
57 
58    if ((name = authname(auth)) == NULL)
59       return 0; /* no username, no match. */
60 
61    do
62       if (strcmp(name, userlist->name) == 0)
63          break;
64    while ((userlist = userlist->next) != NULL);
65 
66    if (userlist == NULL)
67       return 0; /* no match. */
68    return 1;
69 }
70 
71 int
groupmatch(auth,grouplist)72 groupmatch(auth, grouplist)
73    const authmethod_t *auth;
74    const linkedname_t *grouplist;
75 {
76    const char *function = "groupmatch()";
77    const char *username;
78    struct passwd *pw;
79    struct group *groupent;
80 
81    SASSERTX(grouplist != NULL);
82 
83    if ((username = authname(auth)) == NULL)
84       return 0; /* no username, no match. */
85 
86    /*
87     * First check the primary group of the user against grouplist.
88     * If the groupname given there matches, we don't need to go through
89     * all users in the list of group.
90     */
91    if ((pw = getpwnam(username))         != NULL
92    &&  (groupent = getgrgid(pw->pw_gid)) != NULL) {
93       const linkedname_t *listent = grouplist;
94 
95       do
96          if (strcmp(groupent->gr_name, listent->name) == 0)
97             return 1;
98       while ((listent = listent->next) != NULL);
99    }
100    else {
101       if (pw == NULL)
102          slog(LOG_DEBUG, "%s: unknown username \"%s\"", function, username);
103       else if (groupent == NULL)
104          slog(LOG_DEBUG, "%s: unknown primary groupid %ld",
105               function, (long)pw->pw_gid);
106    }
107 
108    /*
109     * Go through grouplist, matching username against each groupmember of
110     * all the groups in grouplist.
111     */
112    do {
113       char **groupname;
114 
115       if ((groupent = getgrnam(grouplist->name)) == NULL) {
116          swarn("%s: unknown groupname \"%s\"", function, grouplist->name);
117          continue;
118       }
119 
120       groupname = groupent->gr_mem;
121 
122       while (*groupname != NULL) {
123          if (strcmp(username, *groupname) == 0)
124             return 1; /* match. */
125 
126          ++groupname;
127       }
128    } while ((grouplist = grouplist->next) != NULL);
129 
130    return 0;
131 }
132 
133 #if HAVE_LDAP
134 int
ldapgroupmatch(auth,rule)135 ldapgroupmatch(auth, rule)
136    const authmethod_t *auth;
137    const rule_t *rule;
138 {
139    const char *function = "ldapgroupmatch()";
140    const linkedname_t *grouplist;
141    const char *username;
142    char *userdomain;
143    int retval;
144 
145    if ((username = authname(auth)) == NULL)
146       return 0; /* no username, no match. */
147 
148 #if !HAVE_GSSAPI
149 
150    if (!rule->state.ldapauthorisation.ldapurl)
151       SERRX(rule->state.ldapauthorisation.ldapurl != NULL);
152 
153 #endif /* !HAVE_GSSAPI */
154 
155    if ((userdomain = strchr(username, '@')) != NULL)
156       ++userdomain;
157 
158    if (userdomain == NULL && *rule->state.ldapauthorisation.domain == NUL
159    &&  rule->state.ldapauthorisation.ldapurl == NULL) {
160       slog(LOG_WARNING,
161            "%s: cannot check ldap group membership for user %s: username has no "
162            "domain postfix and no ldap.domain value is set",
163            function, username);
164 
165       return 0;
166    }
167 
168    if ((retval = ldap_user_is_cached(username, rule->number)) >= 0)
169       return retval;
170 
171    /*
172     * go through grouplist, matching username against members of each group.
173     */
174 
175    grouplist = rule->ldapgroup;
176    do {
177       char *groupdomain;
178       char groupname[MAXNAMELEN];
179 
180       slog(LOG_DEBUG, "%s: checking if user %s is member of ldap group %s",
181            function, username, grouplist->name);
182 
183       STRCPY_ASSERTLEN(groupname, grouplist->name);
184 
185       if ((groupdomain = strchr(groupname, '@')) != NULL) {
186          *groupdomain = NUL; /* separates groupname from groupdomain. */
187          ++groupdomain;
188       }
189 
190       if (groupdomain != NULL && userdomain != NULL) {
191          if (strcmp(groupdomain, userdomain) != 0
192          &&  strcmp(groupdomain, "") != 0) {
193             slog(LOG_DEBUG,
194                  "%s: userdomain \"%s\" does not match groupdomain "
195                  "\"%s\" and groupdomain is not default domain.  "
196                  "Trying next entry",
197                  function, userdomain, groupdomain);
198 
199             continue;
200          }
201       }
202 
203       if (ldapgroupmatches(auth,
204                            username,
205                            userdomain,
206                            groupname,
207                            groupdomain,
208                            rule)) {
209          cache_ldap_user(username, 1, rule->number);
210          return 1;
211       }
212    } while ((grouplist = grouplist->next) != NULL);
213 
214    cache_ldap_user(username, 0, rule->number);
215    return 0;
216 }
217 
218 #endif /* HAVE_LDAP */
219 
220 #if HAVE_PAC
221 int
sidmatch(auth,objectsids)222 sidmatch(auth, objectsids)
223    const authmethod_t *auth;
224    const linkedname_t *objectsids;
225 {
226    const char *function = "sidmatch()";
227    const linkedname_t *grouplist;
228 
229    if (authsids(auth) == NULL) {
230       slog(LOG_DEBUG, "%s: no pac sids found", function);
231 
232       return 0; /* no username, no match. */
233    }
234 
235    /* go through grouplist, matching username against members of each group. */
236    grouplist = objectsids;
237    do {
238       char sids[strlen((const char *)authsids(auth)) + 1];
239       char groupname[MAXNAMELEN];
240       char tsid[MAX_BASE64_LEN];
241       char *token;
242       int convres;
243 
244       STRCPY_ASSERTLEN(sids, authsids(auth));
245 
246       slog(LOG_DEBUG, "%s: checking if a sid in pac sids %s matches %s",
247            function, sids, grouplist->name);
248 
249       STRCPY_ASSERTLEN(groupname, grouplist->name);
250 
251       token = strtok(sids, " ");
252       while (token) {
253          if (!strcmp(token, grouplist->name)) {
254             if ((convres = b64tosid(token, tsid, sizeof(tsid))) == 0)
255                slog(LOG_DEBUG, "%s: user sid %s matches group sid %s/%s",
256                     function, token, tsid, grouplist->name);
257             else {
258                slog(LOG_DEBUG, "%s: user sid %s matches group sid %s",
259                     function, token, grouplist->name);
260 #if DIAGNOSTIC
261                SERRX(convres);
262 #endif /* DEBUG */
263             }
264 
265             return 1;
266          }
267 
268          token = strtok(NULL, " ");
269       }
270 
271       if ((convres = b64tosid(grouplist->name, tsid, sizeof(tsid))) == 0)
272          slog(LOG_DEBUG, "%s: user sids do not match group sid %s/%s",
273               function, tsid, grouplist->name);
274       else {
275          slog(LOG_DEBUG, "%s: user sids do not match group sid %s",
276               function, grouplist->name);
277 
278 #if DIAGNOSTIC
279          SERRX(convres);
280 #endif /* DEBUG */
281       }
282 
283    } while ((grouplist = grouplist->next) != NULL);
284 
285    slog(LOG_DEBUG, "%s: user sids do not match any group", function);
286    return 0;
287 }
288 #endif /* HAVE_PAC */
289 
290 int
accesscheck(s,auth,src,dst,emsg,emsgsize)291 accesscheck(s, auth, src, dst, emsg, emsgsize)
292    int s;
293    authmethod_t *auth;
294    const struct sockaddr_storage *src, *dst;
295    char *emsg;
296    size_t emsgsize;
297 {
298    const char *function = "accesscheck()";
299    char srcstr[MAXSOCKADDRSTRING], dststr[sizeof(srcstr)];
300    int match, authresultisfixed;
301 
302    if (sockscf.option.debug)
303       slog(LOG_DEBUG, "%s: method: %s, %s -> %s ",
304            function,
305            method2string(auth->method),
306            src == NULL ?
307                   "<unknown>" : sockaddr2string(src, srcstr, sizeof(srcstr)),
308             dst == NULL ?
309                  "<unknown>"  : sockaddr2string(dst, dststr, sizeof(dststr)));
310 
311    /*
312     * We don't want to re-check the same method.  This could
313     * happen in several cases:
314     *  - was checked as client-rule, is now checked as socks-rule.
315     *  - a different rule with the same method.  The client is however
316     *    the same, so if the auth for method 'k' failed in previous rule,
317     *    it will fail the next time also.
318    */
319 
320    if (methodisset(auth->method, auth->methodv, (size_t)auth->methodc)) {
321       slog(LOG_DEBUG, "%s: method %s already checked, matches",
322            function, method2string(auth->method));
323 
324       return 1; /* already checked, matches. */
325    }
326 
327    if (methodisset(auth->method, auth->badmethodv, (size_t)auth->badmethodc)) {
328       snprintf(emsg, emsgsize,
329               "authentication provided by client for method \"%s\" "
330               "does not match",
331               method2string(auth->method));
332 
333       slog(LOG_DEBUG, "%s: method %s already checked, does not match",
334            function, method2string(auth->method));
335 
336       return 0; /* already checked, won't match. */
337    }
338 
339    match = 0;
340 
341    switch (auth->method) {
342       /*
343        * Methods where no further checking is done at this point, either
344        * because there's nothing to check, or it has already been checked.
345        */
346       case AUTHMETHOD_NONE:
347 #if HAVE_GSSAPI
348       case AUTHMETHOD_GSSAPI:
349 #endif /* HAVE_GSSAPI */
350          match = 1;
351          break;
352 
353       case AUTHMETHOD_UNAME:
354          if (passwordcheck((const char *)auth->mdata.uname.name,
355                            (const char *)auth->mdata.uname.password,
356                            emsg,
357                            emsgsize) == 0)
358             match = 1;
359 
360          break;
361 
362 #if HAVE_LIBWRAP
363       case AUTHMETHOD_RFC931:
364          if (passwordcheck((const char *)auth->mdata.rfc931.name,
365                            NULL,
366                            emsg,
367                            emsgsize) == 0)
368             match = 1;
369          break;
370 #endif /* HAVE_LIBWRAP */
371 
372 #if HAVE_PAM
373       case AUTHMETHOD_PAM_ANY:
374       case AUTHMETHOD_PAM_ADDRESS:
375       case AUTHMETHOD_PAM_USERNAME: {
376 #if DIAGNOSTIC
377          const int freec
378          = freedescriptors(sockscf.option.debug ?  "start" : NULL, NULL);
379 #endif /* DIAGNOSTIC */
380 
381          if (pam_passwordcheck(s,
382                                src,
383                                dst,
384                                &auth->mdata.pam,
385                                emsg,
386                                emsgsize) == 0)
387             match = 1;
388 
389 #if DIAGNOSTIC
390          if (freec
391          != freedescriptors(sockscf.option.debug ? "end" : NULL, NULL))
392             swarnx("%s: lost %d file descriptor%s in pam_passwordcheck()",
393                    function,
394                    freec - freedescriptors(NULL, NULL),
395                    freec - freedescriptors(NULL, NULL) == 1 ? "" : "s");
396 #endif /* DIAGNOSTIC */
397 
398          break;
399       }
400 #endif /* HAVE_PAM */
401 
402 #if HAVE_BSDAUTH
403       case AUTHMETHOD_BSDAUTH: {
404 #if DIAGNOSTIC
405          const int freec
406          = freedescriptors(sockscf.option.debug ?  "start" : NULL, NULL);
407 #endif /* DIAGNOSTIC */
408 
409          if (bsdauth_passwordcheck(s,
410                                    src,
411                                    dst,
412                                    &auth->mdata.bsd,
413                                    emsg,
414                                    emsgsize) == 0)
415             match = 1;
416 
417 #if DIAGNOSTIC
418          if (freec
419          != freedescriptors(sockscf.option.debug ?  "end" : NULL, NULL))
420             swarnx("%s: lost %d file descriptor%s in bsdauth_passwordcheck()",
421                    function,
422                    freec - freedescriptors(NULL, NULL),
423                    freec - freedescriptors(NULL, NULL) == 1 ? "" : "s");
424 #endif /* DIAGNOSTIC */
425          break;
426       }
427 #endif /* HAVE_BSDAUTH */
428 
429 #if HAVE_LDAP
430 
431       case AUTHMETHOD_LDAPAUTH: {
432 
433 #if DIAGNOSTIC
434 
435          const int freec
436 
437          = freedescriptors(sockscf.option.debug ?  "start" : NULL, NULL);
438 #endif /* DIAGNOSTIC */
439 
440          /*
441           * Temporary workaround in Dante 1.4.x for LDAP library performing
442           * slow systemcalls (e.g. connect(2)) that may get interrupted
443           * if admin sends us e.g. frequent SIGINFO-signals.  The LDAP
444           * library does not retry if e.g. its connect(2) gets interrupted,
445           * so to avoid problems, block our own signals while executing the
446           * ldap-code.  Better workaround expected for Dante 1.5.x.
447           */
448          sigset_t oldmask, newmask;
449          int signalswereblocked;
450 
451          sigemptyset(&newmask);
452          sigaddset(&newmask, SIGHUP);
453          sigaddset(&newmask, SIGUSR1);
454 
455 #if HAVE_SIGNAL_SIGINFO
456 
457          sigaddset(&newmask, SIGINFO);
458 
459 #endif /* HAVE_SIGNAL_SIGINFO */
460 
461          if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) == 0)
462             signalswereblocked = 1;
463          else {
464             swarn("%s: sigprocmask(SIG_BLOCK)", function);
465             signalswereblocked = 0;
466          }
467 
468          if (ldapauth_passwordcheck(s,
469                                     src,
470                                     dst,
471                                     &auth->mdata.ldap,
472                                     emsg,
473                                     emsgsize) == 0)
474             match = 1;
475 
476          if (signalswereblocked)
477             if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0)
478                swarn("%s: sigprocmask(SIG_SETMASK)", function);
479 
480 #if DIAGNOSTIC
481 
482          if (freec
483          != freedescriptors(sockscf.option.debug ?  "end" : NULL, NULL))
484             swarnx("%s: lost %d file descriptor%s in ldapauth_passwordcheck()",
485                    function,
486                    freec - freedescriptors(NULL, NULL),
487                    freec - freedescriptors(NULL, NULL) == 1 ? "" : "s");
488 
489 #endif /* DIAGNOSTIC */
490 
491          break;
492       }
493 
494 #endif /* HAVE_LDAP */
495 
496       default:
497          SERRX(auth->method);
498    }
499 
500    /*
501     * Some methods can be called with different values for the
502     * same client, based on values configured in the rules.
503     * Others cannot and we want to mark those that cannot as
504     * "tried", so we don't waste time on re-trying them.
505     */
506    switch (auth->method) {
507 
508 #if HAVE_PAM
509 
510       case AUTHMETHOD_PAM_ANY:
511       case AUTHMETHOD_PAM_ADDRESS:
512       case AUTHMETHOD_PAM_USERNAME:
513          if (*sockscf.state.pamservicename == NUL)
514             authresultisfixed = 0;
515          else
516             authresultisfixed = 1;
517 
518          break;
519 
520 #endif /* HAVE_PAM */
521 
522 #if HAVE_BSDAUTH
523 
524    case AUTHMETHOD_BSDAUTH:
525          if (sockscf.state.bsdauthstylename == NULL)
526             authresultisfixed = 0;
527          else
528             authresultisfixed = 1;
529 
530          break;
531 
532 #endif /* HAVE_BSDAUTH */
533 
534 #if HAVE_LDAP
535 
536    case AUTHMETHOD_LDAPAUTH:
537          if (sockscf.state.ldapauthentication.ldapurl == NULL)
538             authresultisfixed = 0;
539          else
540             authresultisfixed = 1;
541 
542          break;
543 
544 #endif /* HAVE_LDAP */
545 
546 #if HAVE_GSSAPI
547 
548       case AUTHMETHOD_GSSAPI:
549          if (*sockscf.state.gssapiservicename == NUL
550          ||  *sockscf.state.gssapikeytab      == NUL)
551             authresultisfixed = 0;
552          else
553             authresultisfixed = 1;
554 
555          break;
556 
557 #endif /* HAVE_GSSAPI */
558 
559       case AUTHMETHOD_NONE:
560       case AUTHMETHOD_UNAME:
561       case AUTHMETHOD_RFC931:
562          authresultisfixed = 1;
563          break;
564 
565       default:
566          SERRX(auth->method);
567    }
568 
569    if (authresultisfixed) {
570       if (match) {
571          SASSERTX(auth->methodc + 1 <= ELEMENTS(auth->methodv));
572          auth->methodv[auth->methodc++] = auth->method;
573       }
574       else {
575          SASSERTX(auth->badmethodc + 1 <= ELEMENTS(auth->badmethodv));
576          auth->badmethodv[auth->badmethodc++] = auth->method;
577       }
578 
579       /*
580        * Unfortunately we can not bzero() the password for several reasons:
581        * 1) If UDP, perhaps packets to different targets will require different
582        *    authentication.  E.g. username was used when establishing the
583        *    control-connection, while pam is used when sending packets to
584        *    certain targets.  Unlikely, but not impossible.
585        *
586        * 2) If we are forwarding to an upstream proxy that we are
587        *    configured to offer the username/password from the user.
588        */
589    }
590 
591    if (match)
592       slog(LOG_DEBUG, "%s: authentication matched", function);
593    else
594       slog(LOG_DEBUG, "%s: no match for authentication%s %s",
595            function,
596            emsgsize > 0 ? ":"  : "",
597            emsgsize > 0 ? emsg : "");
598 
599    return match;
600 }
601