summaryrefslogtreecommitdiff
path: root/src/assuan-logging.c
blob: 8ee6aa286dc0a4355036ac89e274415195ba34d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/* assuan-logging.c - Default logging function.
 * Copyright (C) 2002, 2003, 2004, 2007, 2009,
 *               2010 Free Software Foundation, Inc.
 *
 * This file is part of Assuan.
 *
 * Assuan is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * Assuan 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
 * SPDX-License-Identifier: LGPL-2.1+
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
#  include <winsock2.h>
# endif
# include <windows.h>
#endif /*HAVE_W32_SYSTEM*/
#include <errno.h>
#include <ctype.h>

#include "assuan-defs.h"


/* The default log handler is useful for global logging, but it should
   only be used by one user of libassuan at a time.  Libraries that
   use libassuan can register their own log handler.  */

/* A common prefix for all log messages.  */
static char prefix_buffer[80];

/* A global flag read from the environment to check if to enable full
   logging of buffer data.  This is also used by custom log
   handlers.  */
static int full_logging;

/* A bitfield that specifies the categories to log.  */
static int log_cats;
#define TEST_LOG_CAT(x) (!! (log_cats & (1 << (x - 1))))

static FILE *_assuan_log;


void
_assuan_init_log_envvars (void)
{
  char *flagstr;

  full_logging = !!getenv ("ASSUAN_FULL_LOGGING");
  flagstr = getenv ("ASSUAN_DEBUG");
  if (flagstr)
    log_cats = atoi (flagstr);
  else /* Default to log the control channel.  */
    log_cats = (1 << (ASSUAN_LOG_CONTROL - 1));

  _assuan_sysutils_blurb (); /* Make sure this code gets linked in.  */
}


void
assuan_set_assuan_log_stream (FILE *fp)
{
  _assuan_log = fp;

  _assuan_init_log_envvars ();
}


/* Set the per context log stream.  Also enable the default log stream
   if it has not been set.  */
void
assuan_set_log_stream (assuan_context_t ctx, FILE *fp)
{
  if (ctx)
    {
      if (ctx->log_fp)
        fflush (ctx->log_fp);
      ctx->log_fp = fp;
      if (! _assuan_log)
	assuan_set_assuan_log_stream (fp);
    }
}


/* Set the prefix to be used for logging to TEXT or resets it to the
   default if TEXT is NULL. */
void
assuan_set_assuan_log_prefix (const char *text)
{
  if (text)
    {
      strncpy (prefix_buffer, text, sizeof (prefix_buffer)-1);
      prefix_buffer[sizeof (prefix_buffer)-1] = 0;
    }
  else
    *prefix_buffer = 0;
}


/* Get the prefix to be used for logging.  */
const char *
assuan_get_assuan_log_prefix (void)
{
  return prefix_buffer;
}


/* Default log handler.  */
int
_assuan_log_handler (assuan_context_t ctx, void *hook, unsigned int cat,
		     const char *msg)
{
  FILE *fp;
  const char *prf;
  int saved_errno = errno;

  /* For now.  */
  if (msg == NULL)
    return TEST_LOG_CAT (cat);

  if (! TEST_LOG_CAT (cat))
    return 0;

  fp = ctx->log_fp ? ctx->log_fp : _assuan_log;
  if (!fp)
    return 0;

  prf = assuan_get_assuan_log_prefix ();
  if (*prf)
    fprintf (fp, "%s[%u]: ", prf, (unsigned int)getpid ());

  fprintf (fp, "%s", msg);
  /* If the log stream is a file, the output would be buffered.  This
     is bad for debugging, thus we flush the stream if FORMAT ends
     with a LF.  */
  if (msg && *msg && msg[strlen (msg) - 1] == '\n')
    fflush (fp);
  gpg_err_set_errno (saved_errno);

  return 0;
}



/* Log a control channel message.  This is either a STRING with a
   diagnostic or actual data in (BUFFER1,LENGTH1) and
   (BUFFER2,LENGTH2).  If OUTBOUND is true the data is intended for
   the peer.  */
void
_assuan_log_control_channel (assuan_context_t ctx, int outbound,
                             const char *string,
                             const void *buffer1, size_t length1,
                             const void *buffer2, size_t length2)
{
  int res;
  char *outbuf;
  int saved_errno;

  /* Check whether logging is enabled and do a quick check to see
     whether the callback supports our category.  */
  if (!ctx
      || !ctx->log_cb
      || ctx->flags.no_logging
      || !(*ctx->log_cb) (ctx, ctx->log_cb_data, ASSUAN_LOG_CONTROL, NULL))
    return;

  saved_errno = errno;

  /* Note that we use the inbound channel fd as the printed channel
     number for both directions.  */
#ifdef HAVE_W32_SYSTEM
# define CHANNEL_FMT "%p"
#else
# define CHANNEL_FMT "%d"
#endif
#define TOHEX(val) (((val) < 10) ? ((val) + '0') : ((val) - 10 + 'a'))

  if (!buffer1 && buffer2)
    {
      buffer1 = buffer2;
      length1 = length2;
      buffer2 = NULL;
      length2 = 0;
    }

  if (ctx->flags.confidential && !string && buffer1)
    string = "[Confidential data not shown]";

  if (string)
    {
      /* Print the diagnostic.  */
      res = gpgrt_asprintf (&outbuf, "chan_" CHANNEL_FMT " %s [%s]\n",
                            ctx->inbound.fd, outbound? "->":"<-", string);
    }
  else if (buffer1)
    {
      /* Print the control channel data.  */
      const unsigned char *s;
      unsigned int n, x;

      for (n = length1, s = buffer1; n; n--, s++)
        if ((!isascii (*s) || iscntrl (*s) || !isprint (*s) || !*s)
            && !(*s >= 0x80))
          break;
      if (!n && buffer2)
        {
          for (n = length2, s = buffer2; n; n--, s++)
            if ((!isascii (*s) || iscntrl (*s) || !isprint (*s) || !*s)
                && !(*s >= 0x80))
              break;
        }
      if (!buffer2)
        length2 = 0;

      if (!n && (length1 && *(const char*)buffer1 != '['))
        {
          /* No control characters and not starting with our error
             message indicator.  Log it verbatim.  */
          res = gpgrt_asprintf (&outbuf, "chan_" CHANNEL_FMT " %s %.*s%.*s\n",
                          ctx->inbound.fd, outbound? "->":"<-",
                          (int)length1, (const char*)buffer1,
                          (int)length2, buffer2? (const char*)buffer2:"");
        }
      else
        {
          /* The buffer contains control characters - do a hex dump.
             Even in full logging mode we limit the line length -
             however this is no real limit because the provided
             buffers will never be larger than the maximum assuan line
             length. */
          char *hp;
          unsigned int nbytes;
          unsigned int maxbytes = full_logging? (2*LINELENGTH) : 16;

          nbytes = length1 + length2;
          if (nbytes > maxbytes)
            nbytes = maxbytes;

          if (!(outbuf = malloc (50 + 3*nbytes + 60 + 3 + 1)))
            res = -1;
          else
            {
              res = 0;
              hp = outbuf;
              snprintf (hp, 50, "chan_" CHANNEL_FMT " %s [",
                        ctx->inbound.fd, outbound? "->":"<-");
              hp += strlen (hp);
              n = 0;
              for (s = buffer1, x = 0; x < length1 && n < nbytes; x++, n++)
                {
                  *hp++ = ' ';
                  *hp++ = TOHEX (*s >> 4);
                  *hp++ = TOHEX (*s & 0x0f);
                  s++;
                }
              for (s = buffer2, x = 0; x < length2 && n < nbytes; x++, n++)
                {
                  *hp++ = ' ';
                  *hp++ = TOHEX (*s >> 4);
                  *hp++ = TOHEX (*s & 0x0f);
                  s++;
                }
              if (nbytes < length1 + length2)
                {
                  snprintf (hp, 60, " ...(%u byte(s) skipped)",
                            (unsigned int)((length1+length2) - nbytes));
                  hp += strlen (hp);
                }
              strcpy (hp, " ]\n");
            }
        }
    }
  else
    {
      res = 0;
      outbuf = NULL;
    }
  if (res < 0)
    ctx->log_cb (ctx, ctx->log_cb_data, ASSUAN_LOG_CONTROL,
                 "[libassuan failed to format the log message]");
  else if (outbuf)
    {
      ctx->log_cb (ctx, ctx->log_cb_data, ASSUAN_LOG_CONTROL, outbuf);
      gpgrt_free (outbuf);
    }
#undef TOHEX
#undef CHANNEL_FMT
  gpg_err_set_errno (saved_errno);
}