]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/pam-util.c
15a8f5de28f897262b2bc259ea3ac6fa2f7805a5
[thirdparty/systemd.git] / src / shared / pam-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <security/pam_ext.h>
4 #include <syslog.h>
5 #include <stdlib.h>
6
7 #include "alloc-util.h"
8 #include "errno-util.h"
9 #include "format-util.h"
10 #include "macro.h"
11 #include "pam-util.h"
12 #include "process-util.h"
13 #include "stdio-util.h"
14 #include "string-util.h"
15
16 int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) {
17 va_list ap;
18
19 LOCAL_ERRNO(error);
20
21 va_start(ap, format);
22 pam_vsyslog(handle, LOG_ERR, format, ap);
23 va_end(ap);
24
25 return error == -ENOMEM ? PAM_BUF_ERR : PAM_SERVICE_ERR;
26 }
27
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. */
31
32 va_list ap;
33 va_start(ap, format);
34
35 const char *p = endswith(format, "@PAMERR@");
36 if (p) {
37 const char *pamerr = pam_strerror(handle, error);
38 if (strchr(pamerr, '%'))
39 pamerr = "n/a"; /* We cannot have any formatting chars */
40
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);
45 REENABLE_WARNING;
46 } else
47 pam_vsyslog(handle, level, format, ap);
48
49 va_end(ap);
50
51 return error;
52 }
53
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. */
56 struct PamBusData {
57 sd_bus *bus;
58 pam_handle_t *pam_handle;
59 char *cache_id;
60 };
61
62 static PamBusData *pam_bus_data_free(PamBusData *d) {
63 /* The actual destructor */
64 if (!d)
65 return NULL;
66
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
72 * somehow. */
73 sd_bus_flush_close_unref(d->bus);
74 free(d->cache_id);
75
76 /* Note: we don't destroy pam_handle here, because this object is pinned by the handle, and not vice versa! */
77
78 return mfree(d);
79 }
80
81 DEFINE_TRIVIAL_CLEANUP_FUNC(PamBusData*, pam_bus_data_free);
82
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
88 * happen. */
89
90 if (error_status & PAM_DATA_SILENT)
91 pam_syslog(handle, LOG_WARNING, "Attempted to close sd-bus after fork, this should not happen.");
92
93 pam_bus_data_free(data);
94 }
95
96 static char* pam_make_bus_cache_id(const char *module_name) {
97 char *id;
98
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
103 * field ID. */
104
105 if (asprintf(&id, "system-bus-%s-" PID_FMT, ASSERT_PTR(module_name), getpid_cached()) < 0)
106 return NULL;
107
108 return id;
109 }
110
111 void pam_bus_data_disconnectp(PamBusData **_d) {
112 PamBusData *d = *ASSERT_PTR(_d);
113 pam_handle_t *handle;
114 int r;
115
116 /* Disconnects the connection explicitly (for use via _cleanup_()) when called */
117
118 if (!d)
119 return;
120
121 handle = ASSERT_PTR(d->pam_handle); /* Keep a reference to the session even after 'd' might be invalidated */
122
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@");
126
127 /* Note, the pam_set_data() call will invalidate 'd', don't access here anymore */
128 }
129
130 int pam_acquire_bus_connection(
131 pam_handle_t *handle,
132 const char *module_name,
133 sd_bus **ret_bus,
134 PamBusData **ret_pam_bus_data) {
135
136 _cleanup_(pam_bus_data_freep) PamBusData *d = NULL;
137 _cleanup_free_ char *cache_id = NULL;
138 int r;
139
140 assert(handle);
141 assert(module_name);
142 assert(ret_bus);
143
144 cache_id = pam_make_bus_cache_id(module_name);
145 if (!cache_id)
146 return pam_log_oom(handle);
147
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)
151 goto success;
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@");
154
155 d = new(PamBusData, 1);
156 if (!d)
157 return pam_log_oom(handle);
158
159 *d = (PamBusData) {
160 .cache_id = TAKE_PTR(cache_id),
161 .pam_handle = handle,
162 };
163
164 r = sd_bus_open_system(&d->bus);
165 if (r < 0)
166 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to connect to system bus: %m");
167
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@");
171
172 success:
173 *ret_bus = sd_bus_ref(d->bus);
174
175 if (ret_pam_bus_data)
176 *ret_pam_bus_data = d;
177
178 TAKE_PTR(d); /* don't auto-destroy anymore, it's installed now */
179
180 return PAM_SUCCESS;
181 }
182
183 int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) {
184 _cleanup_free_ char *cache_id = NULL;
185 int r;
186
187 assert(module_name);
188
189 cache_id = pam_make_bus_cache_id(module_name);
190 if (!cache_id)
191 return pam_log_oom(handle);
192
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@");
196
197 return PAM_SUCCESS;
198 }
199
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 */
202 free(data);
203 }