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

Generated by: LCOV version 1.14