1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <security/pam_ext.h>
7 #include "alloc-util.h"
8 #include "errno-util.h"
9 #include "format-util.h"
12 #include "process-util.h"
13 #include "stdio-util.h"
14 #include "string-util.h"
16 int pam_syslog_errno(pam_handle_t
*handle
, int level
, int error
, const char *format
, ...) {
22 pam_vsyslog(handle
, LOG_ERR
, format
, ap
);
25 return error
== -ENOMEM
? PAM_BUF_ERR
: PAM_SERVICE_ERR
;
28 int pam_syslog_pam_error(pam_handle_t
*handle
, int level
, int error
, const char *format
, ...) {
29 /* This wraps pam_syslog() but will replace @PAMERR@ with a string from pam_strerror().
30 * @PAMERR@ must be at the very end. */
35 const char *p
= endswith(format
, "@PAMERR@");
37 const char *pamerr
= pam_strerror(handle
, error
);
38 if (strchr(pamerr
, '%'))
39 pamerr
= "n/a"; /* We cannot have any formatting chars */
41 char buf
[p
- format
+ strlen(pamerr
) + 1];
42 xsprintf(buf
, "%*s%s", (int)(p
- format
), format
, pamerr
);
43 DISABLE_WARNING_FORMAT_NONLITERAL
;
44 pam_vsyslog(handle
, level
, buf
, ap
);
47 pam_vsyslog(handle
, level
, format
, ap
);
54 /* A small structure we store inside the PAM session object, that allows us to reuse bus connections but pins
55 * it to the process thoroughly. */
58 pam_handle_t
*pam_handle
;
62 static PamBusData
*pam_bus_data_free(PamBusData
*d
) {
63 /* The actual destructor */
67 /* NB: PAM sessions usually involve forking off a child process, and thus the PAM context might be
68 * duplicated in the child. This destructor might be called twice: both in the parent and in the
69 * child. sd_bus_flush_close_unref() however is smart enough to be a NOP when invoked in any other
70 * process than the one it was invoked from, hence we don't need to add any extra protection here to
71 * ensure that destruction of the bus connection in the child affects the parent's connection
73 sd_bus_flush_close_unref(d
->bus
);
76 /* Note: we don't destroy pam_handle here, because this object is pinned by the handle, and not vice versa! */
81 DEFINE_TRIVIAL_CLEANUP_FUNC(PamBusData
*, pam_bus_data_free
);
83 static void pam_bus_data_destroy(pam_handle_t
*handle
, void *data
, int error_status
) {
84 /* Destructor when called from PAM. Note that error_status is supposed to tell us via PAM_DATA_SILENT
85 * whether we are called in a forked off child of the PAM session or in the original parent. We don't
86 * bother with that however, and instead rely on the PID checks that sd_bus_flush_close_unref() does
87 * internally anyway. That said, we still generate a warning message, since this really shouldn't
90 if (error_status
& PAM_DATA_SILENT
)
91 pam_syslog(handle
, LOG_WARNING
, "Attempted to close sd-bus after fork, this should not happen.");
93 pam_bus_data_free(data
);
96 static char* pam_make_bus_cache_id(const char *module_name
) {
99 /* We want to cache bus connections between hooks. But we don't want to allow them to be reused in
100 * child processes (because sd-bus doesn't support that). We also don't want them to be reused
101 * between our own PAM modules, because they might be linked against different versions of our
102 * utility functions and share different state. Hence include both a module ID and a PID in the data
105 if (asprintf(&id
, "system-bus-%s-" PID_FMT
, ASSERT_PTR(module_name
), getpid_cached()) < 0)
111 void pam_bus_data_disconnectp(PamBusData
**_d
) {
112 PamBusData
*d
= *ASSERT_PTR(_d
);
113 pam_handle_t
*handle
;
116 /* Disconnects the connection explicitly (for use via _cleanup_()) when called */
121 handle
= ASSERT_PTR(d
->pam_handle
); /* Keep a reference to the session even after 'd' might be invalidated */
123 r
= pam_set_data(handle
, ASSERT_PTR(d
->cache_id
), NULL
, NULL
);
124 if (r
!= PAM_SUCCESS
)
125 pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to release PAM user record data, ignoring: @PAMERR@");
127 /* Note, the pam_set_data() call will invalidate 'd', don't access here anymore */
130 int pam_acquire_bus_connection(
131 pam_handle_t
*handle
,
132 const char *module_name
,
134 PamBusData
**ret_pam_bus_data
) {
136 _cleanup_(pam_bus_data_freep
) PamBusData
*d
= NULL
;
137 _cleanup_free_
char *cache_id
= NULL
;
144 cache_id
= pam_make_bus_cache_id(module_name
);
146 return pam_log_oom(handle
);
148 /* We cache the bus connection so that we can share it between the session and the authentication hooks */
149 r
= pam_get_data(handle
, cache_id
, (const void**) &d
);
150 if (r
== PAM_SUCCESS
&& d
)
152 if (!IN_SET(r
, PAM_SUCCESS
, PAM_NO_MODULE_DATA
))
153 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get bus connection: @PAMERR@");
155 d
= new(PamBusData
, 1);
157 return pam_log_oom(handle
);
160 .cache_id
= TAKE_PTR(cache_id
),
161 .pam_handle
= handle
,
164 r
= sd_bus_open_system(&d
->bus
);
166 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to connect to system bus: %m");
168 r
= pam_set_data(handle
, d
->cache_id
, d
, pam_bus_data_destroy
);
169 if (r
!= PAM_SUCCESS
)
170 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to set PAM bus data: @PAMERR@");
173 *ret_bus
= sd_bus_ref(d
->bus
);
175 if (ret_pam_bus_data
)
176 *ret_pam_bus_data
= d
;
178 TAKE_PTR(d
); /* don't auto-destroy anymore, it's installed now */
183 int pam_release_bus_connection(pam_handle_t
*handle
, const char *module_name
) {
184 _cleanup_free_
char *cache_id
= NULL
;
189 cache_id
= pam_make_bus_cache_id(module_name
);
191 return pam_log_oom(handle
);
193 r
= pam_set_data(handle
, cache_id
, NULL
, NULL
);
194 if (r
!= PAM_SUCCESS
)
195 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to release PAM user record data: @PAMERR@");
200 void pam_cleanup_free(pam_handle_t
*handle
, void *data
, int error_status
) {
201 /* A generic destructor for pam_set_data() that just frees the specified data */