xref: /freebsd/contrib/pam-krb5/pam-util/logging.c (revision bf6873c5786e333d679a7838d28812febf479a8a)
1 /*
2  * Logging functions for PAM modules.
3  *
4  * Logs errors and debugging messages from PAM modules.  The debug versions
5  * only log anything if debugging was enabled; the crit and err versions
6  * always log.
7  *
8  * The canonical version of this file is maintained in the rra-c-util package,
9  * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
10  *
11  * Written by Russ Allbery <eagle@eyrie.org>
12  * Copyright 2015, 2018, 2020 Russ Allbery <eagle@eyrie.org>
13  * Copyright 2005-2007, 2009-2010, 2012-2013
14  *     The Board of Trustees of the Leland Stanford Junior University
15  *
16  * Permission is hereby granted, free of charge, to any person obtaining a
17  * copy of this software and associated documentation files (the "Software"),
18  * to deal in the Software without restriction, including without limitation
19  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
20  * and/or sell copies of the Software, and to permit persons to whom the
21  * Software is furnished to do so, subject to the following conditions:
22  *
23  * The above copyright notice and this permission notice shall be included in
24  * all copies or substantial portions of the Software.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
29  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
31  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32  * DEALINGS IN THE SOFTWARE.
33  *
34  * SPDX-License-Identifier: MIT
35  */
36 
37 #include <config.h>
38 #ifdef HAVE_KRB5
39 #    include <portable/krb5.h>
40 #endif
41 #include <portable/pam.h>
42 #include <portable/system.h>
43 
44 #include <syslog.h>
45 
46 #include <pam-util/args.h>
47 #include <pam-util/logging.h>
48 
49 #ifndef LOG_AUTHPRIV
50 #    define LOG_AUTHPRIV LOG_AUTH
51 #endif
52 
53 /* Used for iterating through arrays. */
54 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
55 
56 /*
57  * Mappings of PAM flags to symbolic names for logging when entering a PAM
58  * module function.
59  */
60 static const struct {
61     int flag;
62     const char *name;
63 } FLAGS[] = {
64     /* clang-format off */
65     {PAM_CHANGE_EXPIRED_AUTHTOK, "expired"  },
66     {PAM_DELETE_CRED,            "delete"   },
67     {PAM_DISALLOW_NULL_AUTHTOK,  "nonull"   },
68     {PAM_ESTABLISH_CRED,         "establish"},
69     {PAM_PRELIM_CHECK,           "prelim"   },
70     {PAM_REFRESH_CRED,           "refresh"  },
71     {PAM_REINITIALIZE_CRED,      "reinit"   },
72     {PAM_SILENT,                 "silent"   },
73     {PAM_UPDATE_AUTHTOK,         "update"   },
74     /* clang-format on */
75 };
76 
77 
78 /*
79  * Utility function to format a message into newly allocated memory, reporting
80  * an error via syslog if vasprintf fails.
81  */
82 static char *__attribute__((__format__(printf, 1, 0)))
format(const char * fmt,va_list args)83 format(const char *fmt, va_list args)
84 {
85     char *msg;
86 
87     if (vasprintf(&msg, fmt, args) < 0) {
88         syslog(LOG_CRIT | LOG_AUTHPRIV, "vasprintf failed: %m");
89         return NULL;
90     }
91     return msg;
92 }
93 
94 
95 /*
96  * Log wrapper function that adds the user.  Log a message with the given
97  * priority, prefixed by (user <user>) with the account name being
98  * authenticated if known.
99  */
100 static void __attribute__((__format__(printf, 3, 0)))
log_vplain(struct pam_args * pargs,int priority,const char * fmt,va_list args)101 log_vplain(struct pam_args *pargs, int priority, const char *fmt, va_list args)
102 {
103     char *msg;
104 
105     if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
106         return;
107     if (pargs != NULL && pargs->user != NULL) {
108         msg = format(fmt, args);
109         if (msg == NULL)
110             return;
111         pam_syslog(pargs->pamh, priority, "(user %s) %s", pargs->user, msg);
112         free(msg);
113     } else if (pargs != NULL) {
114         pam_vsyslog(pargs->pamh, priority, fmt, args);
115     } else {
116         msg = format(fmt, args);
117         if (msg == NULL)
118             return;
119         syslog(priority | LOG_AUTHPRIV, "%s", msg);
120         free(msg);
121     }
122 }
123 
124 
125 /*
126  * Wrapper around log_vplain with variadic arguments.
127  */
128 static void __attribute__((__format__(printf, 3, 4)))
log_plain(struct pam_args * pargs,int priority,const char * fmt,...)129 log_plain(struct pam_args *pargs, int priority, const char *fmt, ...)
130 {
131     va_list args;
132 
133     va_start(args, fmt);
134     log_vplain(pargs, priority, fmt, args);
135     va_end(args);
136 }
137 
138 
139 /*
140  * Log wrapper function for reporting a PAM error.  Log a message with the
141  * given priority, prefixed by (user <user>) with the account name being
142  * authenticated if known, followed by a colon and the formatted PAM error.
143  * However, do not include the colon and the PAM error if the PAM status is
144  * PAM_SUCCESS.
145  */
146 static void __attribute__((__format__(printf, 4, 0)))
log_pam(struct pam_args * pargs,int priority,int status,const char * fmt,va_list args)147 log_pam(struct pam_args *pargs, int priority, int status, const char *fmt,
148         va_list args)
149 {
150     char *msg;
151 
152     if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
153         return;
154     msg = format(fmt, args);
155     if (msg == NULL)
156         return;
157     if (pargs == NULL)
158         log_plain(NULL, priority, "%s", msg);
159     else if (status == PAM_SUCCESS)
160         log_plain(pargs, priority, "%s", msg);
161     else
162         log_plain(pargs, priority, "%s: %s", msg,
163                   pam_strerror(pargs->pamh, status));
164     free(msg);
165 }
166 
167 
168 /*
169  * The public interfaces.  For each common log level (crit, err, and debug),
170  * generate a putil_<level> function and one for _pam.  Do this with the
171  * preprocessor to save duplicate code.
172  */
173 /* clang-format off */
174 #define LOG_FUNCTION(level, priority)                             \
175     void __attribute__((__format__(printf, 2, 3)))                \
176     putil_ ## level(struct pam_args *pargs, const char *fmt, ...) \
177     {                                                             \
178         va_list args;                                             \
179                                                                   \
180         va_start(args, fmt);                                      \
181         log_vplain(pargs, priority, fmt, args);                   \
182         va_end(args);                                             \
183     }                                                             \
184     void __attribute__((__format__(printf, 3, 4)))                \
185     putil_ ## level ## _pam(struct pam_args *pargs, int status,   \
186                             const char *fmt, ...)                 \
187     {                                                             \
188         va_list args;                                             \
189                                                                   \
190         va_start(args, fmt);                                      \
191         log_pam(pargs, priority, status, fmt, args);              \
192         va_end(args);                                             \
193     }
LOG_FUNCTION(crit,LOG_CRIT)194 LOG_FUNCTION(crit,   LOG_CRIT)
195 LOG_FUNCTION(err,    LOG_ERR)
196 LOG_FUNCTION(notice, LOG_NOTICE)
197 LOG_FUNCTION(debug,  LOG_DEBUG)
198 /* clang-format on */
199 
200 
201 /*
202  * Report entry into a function.  Takes the PAM arguments, the function name,
203  * and the flags and maps the flags to symbolic names.
204  */
205 void
206 putil_log_entry(struct pam_args *pargs, const char *func, int flags)
207 {
208     size_t i, length, offset;
209     char *out = NULL, *nout;
210 
211     if (!pargs->debug)
212         return;
213     if (flags != 0)
214         for (i = 0; i < ARRAY_SIZE(FLAGS); i++) {
215             if (!(flags & FLAGS[i].flag))
216                 continue;
217             if (out == NULL) {
218                 out = strdup(FLAGS[i].name);
219                 if (out == NULL)
220                     break;
221             } else {
222                 length = strlen(FLAGS[i].name);
223                 nout = realloc(out, strlen(out) + length + 2);
224                 if (nout == NULL) {
225                     free(out);
226                     out = NULL;
227                     break;
228                 }
229                 out = nout;
230                 offset = strlen(out);
231                 out[offset] = '|';
232                 memcpy(out + offset + 1, FLAGS[i].name, length);
233                 out[offset + 1 + length] = '\0';
234             }
235         }
236     if (out == NULL)
237         pam_syslog(pargs->pamh, LOG_DEBUG, "%s: entry", func);
238     else {
239         pam_syslog(pargs->pamh, LOG_DEBUG, "%s: entry (%s)", func, out);
240         free(out);
241     }
242 }
243 
244 
245 /*
246  * Report an authentication failure.  This is a separate function since we
247  * want to include various PAM metadata in the log message and put it in a
248  * standard format.  The format here is modeled after the pam_unix
249  * authentication failure message from Linux PAM.
250  */
251 void __attribute__((__format__(printf, 2, 3)))
putil_log_failure(struct pam_args * pargs,const char * fmt,...)252 putil_log_failure(struct pam_args *pargs, const char *fmt, ...)
253 {
254     char *msg;
255     va_list args;
256     const char *ruser = NULL;
257     const char *rhost = NULL;
258     const char *tty = NULL;
259     const char *name = NULL;
260 
261     if (pargs->user != NULL)
262         name = pargs->user;
263     va_start(args, fmt);
264     msg = format(fmt, args);
265     va_end(args);
266     if (msg == NULL)
267         return;
268     pam_get_item(pargs->pamh, PAM_RUSER, (PAM_CONST void **) &ruser);
269     pam_get_item(pargs->pamh, PAM_RHOST, (PAM_CONST void **) &rhost);
270     pam_get_item(pargs->pamh, PAM_TTY, (PAM_CONST void **) &tty);
271 
272     /* clang-format off */
273     pam_syslog(pargs->pamh, LOG_NOTICE, "%s; logname=%s uid=%ld euid=%ld"
274                " tty=%s ruser=%s rhost=%s", msg,
275                (name  != NULL) ? name  : "",
276                (long) getuid(), (long) geteuid(),
277                (tty   != NULL) ? tty   : "",
278                (ruser != NULL) ? ruser : "",
279                (rhost != NULL) ? rhost : "");
280     /* clang-format on */
281 
282     free(msg);
283 }
284 
285 
286 /*
287  * Below are the additional logging functions enabled if built with Kerberos
288  * support, used to report Kerberos errors.
289  */
290 #ifdef HAVE_KRB5
291 
292 
293 /*
294  * Log wrapper function for reporting a Kerberos error.  Log a message with
295  * the given priority, prefixed by (user <user>) with the account name being
296  * authenticated if known, followed by a colon and the formatted Kerberos
297  * error.
298  */
299 __attribute__((__format__(printf, 4, 0))) static void
log_krb5(struct pam_args * pargs,int priority,int status,const char * fmt,va_list args)300 log_krb5(struct pam_args *pargs, int priority, int status, const char *fmt,
301          va_list args)
302 {
303     char *msg;
304     const char *k5_msg = NULL;
305 
306     if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
307         return;
308     msg = format(fmt, args);
309     if (msg == NULL)
310         return;
311     if (pargs != NULL && pargs->ctx != NULL) {
312         k5_msg = krb5_get_error_message(pargs->ctx, status);
313         log_plain(pargs, priority, "%s: %s", msg, k5_msg);
314     } else {
315         log_plain(pargs, priority, "%s", msg);
316     }
317     free(msg);
318     if (k5_msg != NULL)
319         krb5_free_error_message(pargs->ctx, k5_msg);
320 }
321 
322 
323 /*
324  * The public interfaces.  Do this with the preprocessor to save duplicate
325  * code.
326  */
327 /* clang-format off */
328 #define LOG_FUNCTION_KRB5(level, priority)                              \
329     void __attribute__((__format__(printf, 3, 4)))                      \
330     putil_ ## level ## _krb5(struct pam_args *pargs, int status,        \
331                              const char *fmt, ...)                      \
332     {                                                                   \
333         va_list args;                                                   \
334                                                                         \
335         va_start(args, fmt);                                            \
336         log_krb5(pargs, priority, status, fmt, args);                   \
337         va_end(args);                                                   \
338     }
339 LOG_FUNCTION_KRB5(crit,   LOG_CRIT)
340 LOG_FUNCTION_KRB5(err,    LOG_ERR)
341 LOG_FUNCTION_KRB5(notice, LOG_NOTICE)
342 LOG_FUNCTION_KRB5(debug,  LOG_DEBUG)
343 /* clang-format on */
344 
345 #endif /* HAVE_KRB5 */
346