2016-01-12 17:17:22 +03:00
/*
* Unix SMB / CIFS implementation .
*
* CUPS printing backend helper to execute smbspool
*
* Copyright ( C ) 2010 - 2011 Andreas Schneider < asn @ samba . org >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "includes.h"
# include "system/filesys.h"
2019-05-16 18:40:43 +03:00
# include "system/kerberos.h"
2016-01-12 17:17:22 +03:00
# include "system/passwd.h"
# include <errno.h>
2016-04-27 19:01:51 +03:00
# include <stdlib.h>
2016-01-12 17:17:22 +03:00
# include <string.h>
# include <cups/backend.h>
# include "dynconfig/dynconfig.h"
2016-04-29 14:28:42 +03:00
# undef calloc
2016-01-12 17:17:22 +03:00
enum cups_smb_dbglvl_e {
CUPS_SMB_LOG_DEBUG = 0 ,
CUPS_SMB_LOG_ERROR ,
} ;
2017-11-19 21:34:58 +03:00
static void cups_smb_debug ( enum cups_smb_dbglvl_e lvl , const char * format , . . . )
PRINTF_ATTRIBUTE ( 2 , 3 ) ;
2016-01-12 17:17:22 +03:00
# define CUPS_SMB_DEBUG(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
# define CUPS_SMB_ERROR(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
static void cups_smb_debug ( enum cups_smb_dbglvl_e lvl , const char * format , . . . )
{
const char * prefix = " DEBUG " ;
char buffer [ 1024 ] ;
va_list va ;
va_start ( va , format ) ;
vsnprintf ( buffer , sizeof ( buffer ) , format , va ) ;
va_end ( va ) ;
switch ( lvl ) {
case CUPS_SMB_LOG_DEBUG :
prefix = " DEBUG " ;
break ;
case CUPS_SMB_LOG_ERROR :
prefix = " ERROR " ;
break ;
}
fprintf ( stderr ,
" %s: SMBSPOOL_KRB5 - %s \n " ,
prefix ,
buffer ) ;
}
2019-05-16 18:40:43 +03:00
static bool kerberos_get_default_ccache ( char * ccache_buf , size_t len )
{
krb5_context ctx ;
const char * ccache_name = NULL ;
char * full_ccache_name = NULL ;
krb5_ccache ccache = NULL ;
krb5_error_code code ;
code = krb5_init_context ( & ctx ) ;
if ( code ! = 0 ) {
return false ;
}
ccache_name = krb5_cc_default_name ( ctx ) ;
if ( ccache_name = = NULL ) {
krb5_free_context ( ctx ) ;
return false ;
}
code = krb5_cc_resolve ( ctx , ccache_name , & ccache ) ;
if ( code ! = 0 ) {
krb5_free_context ( ctx ) ;
return false ;
}
code = krb5_cc_get_full_name ( ctx , ccache , & full_ccache_name ) ;
krb5_cc_close ( ctx , ccache ) ;
if ( code ! = 0 ) {
krb5_free_context ( ctx ) ;
return false ;
}
snprintf ( ccache_buf , len , " %s " , full_ccache_name ) ;
# ifdef SAMBA4_USES_HEIMDAL
free ( full_ccache_name ) ;
# else
krb5_free_string ( ctx , full_ccache_name ) ;
# endif
krb5_free_context ( ctx ) ;
return true ;
}
2016-01-12 17:17:22 +03:00
/*
* This is a helper binary to execute smbspool .
*
* It needs to be installed or symlinked as :
* / usr / lib / cups / backend / smb
*
* The permissions of the binary need to be set to 0700 so that it is executed
* as root . The binary switches to the user which is passed via the environment
* variable AUTH_UID , so we can access the kerberos ticket .
*/
int main ( int argc , char * argv [ ] )
{
char smbspool_cmd [ PATH_MAX ] = { 0 } ;
struct passwd * pwd ;
2019-05-13 17:55:49 +03:00
struct group * g = NULL ;
2016-01-12 17:17:22 +03:00
char gen_cc [ PATH_MAX ] = { 0 } ;
2019-03-12 11:40:58 +03:00
char * env = NULL ;
char auth_info_required [ 256 ] = { 0 } ;
char device_uri [ 4096 ] = { 0 } ;
2016-01-12 17:17:22 +03:00
uid_t uid = ( uid_t ) - 1 ;
gid_t gid = ( gid_t ) - 1 ;
2019-05-13 17:55:49 +03:00
gid_t groups [ 1 ] = { ( gid_t ) - 1 } ;
2016-01-12 17:17:22 +03:00
unsigned long tmp ;
2019-05-16 18:40:43 +03:00
bool ok ;
2016-01-12 17:17:22 +03:00
int cmp ;
int rc ;
2019-03-12 11:40:58 +03:00
env = getenv ( " DEVICE_URI " ) ;
if ( env ! = NULL & & strlen ( env ) > 2 ) {
snprintf ( device_uri , sizeof ( device_uri ) , " %s " , env ) ;
}
2019-11-03 01:28:13 +03:00
/* We must handle the following values of AUTH_INFO_REQUIRED:
* none : Anonymous / guest printing
* username , password : A username ( of the form " username " or " DOMAIN \ username " )
* and password are required
* negotiate : Kerberos authentication
* NULL ( not set ) : will never happen when called from cupsd
* https : //www.cups.org/doc/spec-ipp.html#auth-info-required
* https : //github.com/apple/cups/issues/5674
*/
2016-01-12 17:17:22 +03:00
env = getenv ( " AUTH_INFO_REQUIRED " ) ;
2017-02-16 09:57:42 +03:00
/* If not set, then just call smbspool. */
2019-03-12 11:40:58 +03:00
if ( env = = NULL | | env [ 0 ] = = 0 ) {
2017-07-07 15:08:49 +03:00
CUPS_SMB_DEBUG ( " AUTH_INFO_REQUIRED is not set - "
2019-11-03 01:28:13 +03:00
" executing smbspool " ) ;
/* Pass this printing task to smbspool without Kerberos auth */
2017-07-07 15:08:49 +03:00
goto smbspool ;
2017-02-16 09:57:42 +03:00
} else {
2017-07-07 15:08:49 +03:00
CUPS_SMB_DEBUG ( " AUTH_INFO_REQUIRED=%s " , env ) ;
2019-11-03 01:28:13 +03:00
/* First test the value of AUTH_INFO_REQUIRED
* against known possible values
*/
2019-10-28 11:38:08 +03:00
cmp = strcmp ( env , " none " ) ;
if ( cmp = = 0 ) {
CUPS_SMB_DEBUG ( " Authenticate using none (anonymous) - "
2019-11-03 01:28:13 +03:00
" executing smbspool " ) ;
2019-10-28 11:38:08 +03:00
goto smbspool ;
}
2019-03-12 11:40:58 +03:00
2017-07-07 15:08:49 +03:00
cmp = strcmp ( env , " username,password " ) ;
if ( cmp = = 0 ) {
CUPS_SMB_DEBUG ( " Authenticate using username/password - "
2019-11-03 01:28:13 +03:00
" executing smbspool " ) ;
2017-07-07 15:08:49 +03:00
goto smbspool ;
}
2019-11-03 01:28:13 +03:00
/* Now, if 'goto smbspool' still has not happened,
* there are only two variants left :
* 1 ) AUTH_INFO_REQUIRED is " negotiate " and then
* we have to continue working
* 2 ) or it is something not known to us , then Kerberos
* authentication is not required , so just also pass
* this task to smbspool
*/
2017-07-07 15:08:49 +03:00
cmp = strcmp ( env , " negotiate " ) ;
if ( cmp ! = 0 ) {
2019-11-03 01:28:13 +03:00
CUPS_SMB_DEBUG ( " Value of AUTH_INFO_REQUIRED is not known "
" to smbspool_krb5_wrapper, executing smbspool " ) ;
goto smbspool ;
2017-07-07 15:08:49 +03:00
}
2019-10-28 11:38:08 +03:00
snprintf ( auth_info_required ,
sizeof ( auth_info_required ) ,
" %s " ,
env ) ;
2016-01-12 17:17:22 +03:00
}
2017-02-16 09:57:42 +03:00
uid = getuid ( ) ;
CUPS_SMB_DEBUG ( " Started with uid=%d \n " , uid ) ;
if ( uid ! = 0 ) {
goto smbspool ;
2016-01-12 17:17:22 +03:00
}
/*
* AUTH_UID gets only set if we have an incoming connection over the
* CUPS unix domain socket .
*/
env = getenv ( " AUTH_UID " ) ;
if ( env = = NULL ) {
CUPS_SMB_ERROR ( " AUTH_UID is not set " ) ;
fprintf ( stderr , " ATTR: auth-info-required=negotiate \n " ) ;
return CUPS_BACKEND_AUTH_REQUIRED ;
}
if ( strlen ( env ) > 10 ) {
CUPS_SMB_ERROR ( " Invalid AUTH_UID " ) ;
return CUPS_BACKEND_FAILED ;
}
errno = 0 ;
tmp = strtoul ( env , NULL , 10 ) ;
if ( errno ! = 0 | | tmp > = UINT32_MAX ) {
CUPS_SMB_ERROR ( " Failed to convert AUTH_UID=%s " , env ) ;
return CUPS_BACKEND_FAILED ;
}
uid = ( uid_t ) tmp ;
2019-11-29 10:28:28 +03:00
/* If we are printing as the root user, we're done here. */
if ( uid = = 0 ) {
goto smbspool ;
}
2016-01-12 17:17:22 +03:00
pwd = getpwuid ( uid ) ;
if ( pwd = = NULL ) {
CUPS_SMB_ERROR ( " Failed to find system user: %u - %s " ,
uid , strerror ( errno ) ) ;
return CUPS_BACKEND_FAILED ;
}
gid = pwd - > pw_gid ;
rc = setgroups ( 0 , NULL ) ;
if ( rc ! = 0 ) {
CUPS_SMB_ERROR ( " Failed to clear groups - %s " ,
strerror ( errno ) ) ;
return CUPS_BACKEND_FAILED ;
}
2019-05-13 17:55:49 +03:00
/*
* We need the primary group of the ' lp ' user . This is needed to access
* temporary files in / var / spool / cups / .
*/
g = getgrnam ( " lp " ) ;
if ( g = = NULL ) {
CUPS_SMB_ERROR ( " Failed to find user 'lp' - %s " ,
strerror ( errno ) ) ;
return CUPS_BACKEND_FAILED ;
}
CUPS_SMB_DEBUG ( " Adding group 'lp' (%u) " , g - > gr_gid ) ;
groups [ 0 ] = g - > gr_gid ;
rc = setgroups ( sizeof ( groups ) , groups ) ;
if ( rc ! = 0 ) {
CUPS_SMB_ERROR ( " Failed to set groups for 'lp' - %s " ,
strerror ( errno ) ) ;
return CUPS_BACKEND_FAILED ;
}
2016-01-12 17:17:22 +03:00
CUPS_SMB_DEBUG ( " Switching to gid=%d " , gid ) ;
rc = setgid ( gid ) ;
if ( rc ! = 0 ) {
2017-11-19 21:34:58 +03:00
CUPS_SMB_ERROR ( " Failed to switch to gid=%u - %s " ,
2016-01-12 17:17:22 +03:00
gid ,
strerror ( errno ) ) ;
return CUPS_BACKEND_FAILED ;
}
CUPS_SMB_DEBUG ( " Switching to uid=%u " , uid ) ;
rc = setuid ( uid ) ;
if ( rc ! = 0 ) {
2017-11-19 21:34:58 +03:00
CUPS_SMB_ERROR ( " Failed to switch to uid=%u - %s " ,
2016-01-12 17:17:22 +03:00
uid ,
strerror ( errno ) ) ;
return CUPS_BACKEND_FAILED ;
}
2017-07-12 17:07:25 +03:00
env = getenv ( " KRB5CCNAME " ) ;
if ( env ! = NULL & & env [ 0 ] ! = 0 ) {
snprintf ( gen_cc , sizeof ( gen_cc ) , " %s " , env ) ;
2019-05-16 15:25:00 +03:00
CUPS_SMB_DEBUG ( " User already set KRB5CCNAME [%s] as ccache " ,
gen_cc ) ;
2017-07-12 17:07:25 +03:00
goto create_env ;
}
2019-05-16 18:40:43 +03:00
ok = kerberos_get_default_ccache ( gen_cc , sizeof ( gen_cc ) ) ;
if ( ok ) {
CUPS_SMB_DEBUG ( " Use default KRB5CCNAME [%s] " ,
gen_cc ) ;
goto create_env ;
2016-01-12 17:17:22 +03:00
}
2019-05-16 18:40:43 +03:00
/* Fallback to a FILE ccache */
snprintf ( gen_cc , sizeof ( gen_cc ) , " FILE:/tmp/krb5cc_%u " , uid ) ;
2017-07-12 17:07:25 +03:00
create_env :
2016-01-12 17:17:22 +03:00
/*
* Make sure we do not have LD_PRELOAD or other security relevant
* environment variables set .
*/
2016-04-27 19:01:51 +03:00
# ifdef HAVE_CLEARENV
2016-01-12 17:17:22 +03:00
clearenv ( ) ;
2016-04-27 19:01:51 +03:00
# else
2021-10-13 19:46:07 +03:00
environ = calloc ( 3 , sizeof ( * environ ) ) ;
2016-04-27 19:01:51 +03:00
# endif
2016-01-12 17:17:22 +03:00
CUPS_SMB_DEBUG ( " Setting KRB5CCNAME to '%s' " , gen_cc ) ;
setenv ( " KRB5CCNAME " , gen_cc , 1 ) ;
2019-03-12 11:40:58 +03:00
if ( device_uri [ 0 ] ! = ' \0 ' ) {
setenv ( " DEVICE_URI " , device_uri , 1 ) ;
}
if ( auth_info_required [ 0 ] ! = ' \0 ' ) {
setenv ( " AUTH_INFO_REQUIRED " , auth_info_required , 1 ) ;
}
2016-01-12 17:17:22 +03:00
smbspool :
snprintf ( smbspool_cmd ,
sizeof ( smbspool_cmd ) ,
" %s/smbspool " ,
get_dyn_BINDIR ( ) ) ;
return execv ( smbspool_cmd , argv ) ;
}