LCOV - code coverage report
Current view: top level - source3/client - smbspool_krb5_wrapper.c (source / functions) Hit Total Coverage
Test: coverage report for master 98b443d9 Lines: 42 140 30.0 %
Date: 2024-05-31 13:13:24 Functions: 2 3 66.7 %

          Line data    Source code
       1             : /*
       2             :  * Unix SMB/CIFS implementation.
       3             :  *
       4             :  * CUPS printing backend helper to execute smbspool
       5             :  *
       6             :  * Copyright (C) 2010-2011 Andreas Schneider <asn@samba.org>
       7             :  *
       8             :  * This program is free software; you can redistribute it and/or modify
       9             :  * it under the terms of the GNU General Public License as published by
      10             :  * the Free Software Foundation; either version 3 of the License, or
      11             :  * (at your option) any later version.
      12             :  *
      13             :  * This program is distributed in the hope that it will be useful,
      14             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             :  * GNU General Public License for more details.
      17             :  *
      18             :  * You should have received a copy of the GNU General Public License
      19             :  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      20             :  */
      21             : 
      22             : #include "includes.h"
      23             : #include "system/filesys.h"
      24             : #include "system/kerberos.h"
      25             : #include "system/passwd.h"
      26             : #include "lib/krb5_wrap/krb5_samba.h"
      27             : 
      28             : #include <cups/backend.h>
      29             : 
      30             : #include "dynconfig/dynconfig.h"
      31             : 
      32             : #undef calloc
      33             : 
      34             : enum cups_smb_dbglvl_e {
      35             :         CUPS_SMB_LOG_DEBUG = 0,
      36             :         CUPS_SMB_LOG_ERROR,
      37             : };
      38             : static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
      39             :                 PRINTF_ATTRIBUTE(2, 3);
      40             : 
      41             : #define CUPS_SMB_DEBUG(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
      42             : #define CUPS_SMB_ERROR(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
      43             : 
      44          14 : static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
      45             : {
      46          14 :         const char *prefix = "DEBUG";
      47             :         char buffer[1024];
      48             :         va_list va;
      49             : 
      50          14 :         va_start(va, format);
      51          14 :         vsnprintf(buffer, sizeof(buffer), format, va);
      52          14 :         va_end(va);
      53             : 
      54          14 :         switch (lvl) {
      55          14 :         case CUPS_SMB_LOG_DEBUG:
      56          14 :                 prefix = "DEBUG";
      57          14 :                 break;
      58           0 :         case CUPS_SMB_LOG_ERROR:
      59           0 :                 prefix = "ERROR";
      60           0 :                 break;
      61             :         }
      62             : 
      63          14 :         fprintf(stderr,
      64             :                 "%s: SMBSPOOL_KRB5 - %s\n",
      65             :                 prefix,
      66             :                 buffer);
      67          14 : }
      68             : 
      69           0 : static bool kerberos_get_default_ccache(char *ccache_buf, size_t len)
      70             : {
      71             :         krb5_context ctx;
      72           0 :         const char *ccache_name = NULL;
      73           0 :         char *full_ccache_name = NULL;
      74           0 :         krb5_ccache ccache = NULL;
      75             :         krb5_error_code code;
      76             : 
      77           0 :         code = krb5_init_context(&ctx);
      78           0 :         if (code != 0) {
      79           0 :                 return false;
      80             :         }
      81             : 
      82           0 :         ccache_name = smb_force_krb5_cc_default_name(ctx);
      83           0 :         if (ccache_name == NULL) {
      84           0 :                 krb5_free_context(ctx);
      85           0 :                 return false;
      86             :         }
      87             : 
      88           0 :         code = krb5_cc_resolve(ctx, ccache_name, &ccache);
      89           0 :         if (code != 0) {
      90           0 :                 krb5_free_context(ctx);
      91           0 :                 return false;
      92             :         }
      93             : 
      94           0 :         code = krb5_cc_get_full_name(ctx, ccache, &full_ccache_name);
      95           0 :         krb5_cc_close(ctx, ccache);
      96           0 :         if (code != 0) {
      97           0 :                 krb5_free_context(ctx);
      98           0 :                 return false;
      99             :         }
     100             : 
     101           0 :         snprintf(ccache_buf, len, "%s", full_ccache_name);
     102             : 
     103             : #ifdef SAMBA4_USES_HEIMDAL
     104           0 :         free(full_ccache_name);
     105             : #else
     106           0 :         krb5_free_string(ctx, full_ccache_name);
     107             : #endif
     108           0 :         krb5_free_context(ctx);
     109             : 
     110           0 :         return true;
     111             : }
     112             : 
     113             : /*
     114             :  * This is a helper binary to execute smbspool.
     115             :  *
     116             :  * It needs to be installed or symlinked as:
     117             :  *      /usr/lib/cups/backend/smb
     118             :  *
     119             :  * The permissions of the binary need to be set to 0700 so that it is executed
     120             :  * as root. The binary switches to the user which is passed via the environment
     121             :  * variable AUTH_UID, so we can access the kerberos ticket.
     122             :  */
     123           8 : int main(int argc, char *argv[])
     124             : {
     125           8 :         char smbspool_cmd[PATH_MAX] = {0};
     126             :         struct passwd *pwd;
     127           8 :         struct group *g = NULL;
     128           8 :         char gen_cc[PATH_MAX] = {0};
     129           8 :         char *env = NULL;
     130           8 :         char auth_info_required[256] = {0};
     131           8 :         char device_uri[4096] = {0};
     132           8 :         uid_t uid = (uid_t)-1;
     133           8 :         gid_t gid = (gid_t)-1;
     134           8 :         gid_t groups[1] = { (gid_t)-1 };
     135             :         unsigned long tmp;
     136             :         bool ok;
     137             :         int cmp;
     138             :         int rc;
     139             : 
     140           8 :         env = getenv("DEVICE_URI");
     141           8 :         if (env != NULL && strlen(env) > 2) {
     142           0 :                 snprintf(device_uri, sizeof(device_uri), "%s", env);
     143             :         }
     144             : 
     145             :         /* We must handle the following values of AUTH_INFO_REQUIRED:
     146             :          *  none: Anonymous/guest printing
     147             :          *  username,password: A username (of the form "username" or "DOMAIN\username")
     148             :          *                     and password are required
     149             :          *  negotiate: Kerberos authentication
     150             :          *  NULL (not set): will never happen when called from cupsd
     151             :          * https://www.cups.org/doc/spec-ipp.html#auth-info-required
     152             :          * https://github.com/apple/cups/issues/5674
     153             :          */
     154           8 :         env = getenv("AUTH_INFO_REQUIRED");
     155             : 
     156             :         /* If not set, then just call smbspool. */
     157           8 :         if (env == NULL || env[0] == 0) {
     158           2 :                 CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED is not set - "
     159             :                                "executing smbspool");
     160             :                 /* Pass this printing task to smbspool without Kerberos auth */
     161           2 :                 goto smbspool;
     162             :         } else {
     163           6 :                 CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED=%s", env);
     164             : 
     165             :                 /* First test the value of AUTH_INFO_REQUIRED
     166             :                  * against known possible values
     167             :                  */
     168           6 :                 cmp = strcmp(env, "none");
     169           6 :                 if (cmp == 0) {
     170           2 :                         CUPS_SMB_DEBUG("Authenticate using none (anonymous) - "
     171             :                                        "executing smbspool");
     172           2 :                         goto smbspool;
     173             :                 }
     174             : 
     175           4 :                 cmp = strcmp(env, "username,password");
     176           4 :                 if (cmp == 0) {
     177           2 :                         CUPS_SMB_DEBUG("Authenticate using username/password - "
     178             :                                        "executing smbspool");
     179           2 :                         goto smbspool;
     180             :                 }
     181             : 
     182             :                 /* Now, if 'goto smbspool' still has not happened,
     183             :                  * there are only two variants left:
     184             :                  * 1) AUTH_INFO_REQUIRED is "negotiate" and then
     185             :                  *    we have to continue working
     186             :                  * 2) or it is something not known to us, then Kerberos
     187             :                  *    authentication is not required, so just also pass
     188             :                  *    this task to smbspool
     189             :                  */
     190           2 :                 cmp = strcmp(env, "negotiate");
     191           2 :                 if (cmp != 0) {
     192           2 :                         CUPS_SMB_DEBUG("Value of AUTH_INFO_REQUIRED is not known "
     193             :                                        "to smbspool_krb5_wrapper, executing smbspool");
     194           2 :                         goto smbspool;
     195             :                 }
     196             : 
     197           0 :                 snprintf(auth_info_required,
     198             :                          sizeof(auth_info_required),
     199             :                          "%s",
     200             :                          env);
     201             :         }
     202             : 
     203           0 :         uid = getuid();
     204             : 
     205           0 :         CUPS_SMB_DEBUG("Started with uid=%d\n", uid);
     206           0 :         if (uid != 0) {
     207           0 :                 goto smbspool;
     208             :         }
     209             : 
     210             :         /*
     211             :          * AUTH_UID gets only set if we have an incoming connection over the
     212             :          * CUPS unix domain socket.
     213             :          */
     214           0 :         env = getenv("AUTH_UID");
     215           0 :         if (env == NULL) {
     216           0 :                 CUPS_SMB_ERROR("AUTH_UID is not set");
     217           0 :                 fprintf(stderr, "ATTR: auth-info-required=negotiate\n");
     218           0 :                 return CUPS_BACKEND_AUTH_REQUIRED;
     219             :         }
     220             : 
     221           0 :         if (strlen(env) > 10) {
     222           0 :                 CUPS_SMB_ERROR("Invalid AUTH_UID");
     223           0 :                 return CUPS_BACKEND_FAILED;
     224             :         }
     225             : 
     226           0 :         errno = 0;
     227           0 :         tmp = strtoul(env, NULL, 10);
     228           0 :         if (errno != 0 || tmp >= UINT32_MAX) {
     229           0 :                 CUPS_SMB_ERROR("Failed to convert AUTH_UID=%s", env);
     230           0 :                 return CUPS_BACKEND_FAILED;
     231             :         }
     232           0 :         uid = (uid_t)tmp;
     233             : 
     234             :         /* If we are printing as the root user, we're done here. */
     235           0 :         if (uid == 0) {
     236           0 :                 goto smbspool;
     237             :         }
     238             : 
     239           0 :         pwd = getpwuid(uid);
     240           0 :         if (pwd == NULL) {
     241           0 :                 CUPS_SMB_ERROR("Failed to find system user: %u - %s",
     242             :                                uid, strerror(errno));
     243           0 :                 return CUPS_BACKEND_FAILED;
     244             :         }
     245           0 :         gid = pwd->pw_gid;
     246             : 
     247           0 :         rc = setgroups(0, NULL);
     248           0 :         if (rc != 0) {
     249           0 :                 CUPS_SMB_ERROR("Failed to clear groups - %s",
     250             :                                strerror(errno));
     251           0 :                 return CUPS_BACKEND_FAILED;
     252             :         }
     253             : 
     254             :         /*
     255             :          * We need the primary group of the 'lp' user. This is needed to access
     256             :          * temporary files in /var/spool/cups/.
     257             :          */
     258           0 :         g = getgrnam("lp");
     259           0 :         if (g == NULL) {
     260           0 :                 CUPS_SMB_ERROR("Failed to find user 'lp' - %s",
     261             :                                strerror(errno));
     262           0 :                 return CUPS_BACKEND_FAILED;
     263             :         }
     264             : 
     265           0 :         CUPS_SMB_DEBUG("Adding group 'lp' (%u)", g->gr_gid);
     266           0 :         groups[0] = g->gr_gid;
     267           0 :         rc = setgroups(ARRAY_SIZE(groups), groups);
     268           0 :         if (rc != 0) {
     269           0 :                 CUPS_SMB_ERROR("Failed to set groups for 'lp' - %s",
     270             :                                strerror(errno));
     271           0 :                 return CUPS_BACKEND_FAILED;
     272             :         }
     273             : 
     274           0 :         CUPS_SMB_DEBUG("Switching to gid=%d", gid);
     275           0 :         rc = setgid(gid);
     276           0 :         if (rc != 0) {
     277           0 :                 CUPS_SMB_ERROR("Failed to switch to gid=%u - %s",
     278             :                                gid,
     279             :                                strerror(errno));
     280           0 :                 return CUPS_BACKEND_FAILED;
     281             :         }
     282             : 
     283           0 :         CUPS_SMB_DEBUG("Switching to uid=%u", uid);
     284           0 :         rc = setuid(uid);
     285           0 :         if (rc != 0) {
     286           0 :                 CUPS_SMB_ERROR("Failed to switch to uid=%u - %s",
     287             :                                uid,
     288             :                                strerror(errno));
     289           0 :                 return CUPS_BACKEND_FAILED;
     290             :         }
     291             : 
     292           0 :         env = getenv("KRB5CCNAME");
     293           0 :         if (env != NULL && env[0] != 0) {
     294           0 :                 snprintf(gen_cc, sizeof(gen_cc), "%s", env);
     295           0 :                 CUPS_SMB_DEBUG("User already set KRB5CCNAME [%s] as ccache",
     296             :                                gen_cc);
     297             : 
     298           0 :                 goto create_env;
     299             :         }
     300             : 
     301           0 :         ok = kerberos_get_default_ccache(gen_cc, sizeof(gen_cc));
     302           0 :         if (ok) {
     303           0 :                 CUPS_SMB_DEBUG("Use default KRB5CCNAME [%s]",
     304             :                                gen_cc);
     305           0 :                 goto create_env;
     306             :         }
     307             : 
     308             :         /* Fallback to a FILE ccache */
     309           0 :         snprintf(gen_cc, sizeof(gen_cc), "FILE:/tmp/krb5cc_%u", uid);
     310             : 
     311           0 : create_env:
     312             :         /*
     313             :          * Make sure we do not have LD_PRELOAD or other security relevant
     314             :          * environment variables set.
     315             :          */
     316             : #ifdef HAVE_CLEARENV
     317           0 :         clearenv();
     318             : #else
     319             :         environ = calloc(3, sizeof(*environ));
     320             : #endif
     321             : 
     322           0 :         CUPS_SMB_DEBUG("Setting KRB5CCNAME to '%s'", gen_cc);
     323           0 :         setenv("KRB5CCNAME", gen_cc, 1);
     324           0 :         if (device_uri[0] != '\0') {
     325           0 :                 setenv("DEVICE_URI", device_uri, 1);
     326             :         }
     327           0 :         if (auth_info_required[0] != '\0') {
     328           0 :                 setenv("AUTH_INFO_REQUIRED", auth_info_required, 1);
     329             :         }
     330             : 
     331           0 : smbspool:
     332           8 :         snprintf(smbspool_cmd,
     333             :                  sizeof(smbspool_cmd),
     334             :                  "%s/smbspool",
     335             :                  get_dyn_BINDIR());
     336             : 
     337           8 :         return execv(smbspool_cmd, argv);
     338             : }

Generated by: LCOV version 1.14