Coverage Report

Created: 2025-06-24 07:01

/src/cups/cups/sidechannel.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Side-channel API code for CUPS.
3
 *
4
 * Copyright 2007-2014 by Apple Inc.
5
 * Copyright 2006 by Easy Software Products.
6
 *
7
 * These coded instructions, statements, and computer programs are the
8
 * property of Apple Inc. and are protected by Federal copyright
9
 * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
10
 * which should have been included with this file.  If this file is
11
 * missing or damaged, see the license at "http://www.cups.org/".
12
 *
13
 * This file is subject to the Apple OS-Developed Software exception.
14
 */
15
16
/*
17
 * Include necessary headers...
18
 */
19
20
#include "sidechannel.h"
21
#include "cups-private.h"
22
#ifdef _WIN32
23
#  include <io.h>
24
#else
25
#  include <unistd.h>
26
#endif /* _WIN32 */
27
#ifndef _WIN32
28
#  include <sys/select.h>
29
#  include <sys/time.h>
30
#endif /* !_WIN32 */
31
#ifdef HAVE_POLL
32
#  include <poll.h>
33
#endif /* HAVE_POLL */
34
35
36
/*
37
 * Buffer size for side-channel requests...
38
 */
39
40
0
#define _CUPS_SC_MAX_DATA 65535
41
0
#define _CUPS_SC_MAX_BUFFER 65540
42
43
44
/*
45
 * 'cupsSideChannelDoRequest()' - Send a side-channel command to a backend and wait for a response.
46
 *
47
 * This function is normally only called by filters, drivers, or port
48
 * monitors in order to communicate with the backend used by the current
49
 * printer.  Programs must be prepared to handle timeout or "not
50
 * implemented" status codes, which indicate that the backend or device
51
 * do not support the specified side-channel command.
52
 *
53
 * The "datalen" parameter must be initialized to the size of the buffer
54
 * pointed to by the "data" parameter.  cupsSideChannelDoRequest() will
55
 * update the value to contain the number of data bytes in the buffer.
56
 *
57
 * @since CUPS 1.3/macOS 10.5@
58
 */
59
60
cups_sc_status_t      /* O  - Status of command */
61
cupsSideChannelDoRequest(
62
    cups_sc_command_t command,    /* I  - Command to send */
63
    char              *data,    /* O  - Response data buffer pointer */
64
    int               *datalen,   /* IO - Size of data buffer on entry, number of bytes in buffer on return */
65
    double            timeout)    /* I  - Timeout in seconds */
66
0
{
67
0
  cups_sc_status_t  status;   /* Status of command */
68
0
  cups_sc_command_t rcommand; /* Response command */
69
70
71
0
  if (cupsSideChannelWrite(command, CUPS_SC_STATUS_NONE, NULL, 0, timeout))
72
0
    return (CUPS_SC_STATUS_TIMEOUT);
73
74
0
  if (cupsSideChannelRead(&rcommand, &status, data, datalen, timeout))
75
0
    return (CUPS_SC_STATUS_TIMEOUT);
76
77
0
  if (rcommand != command)
78
0
    return (CUPS_SC_STATUS_BAD_MESSAGE);
79
80
0
  return (status);
81
0
}
82
83
84
/*
85
 * 'cupsSideChannelRead()' - Read a side-channel message.
86
 *
87
 * This function is normally only called by backend programs to read
88
 * commands from a filter, driver, or port monitor program.  The
89
 * caller must be prepared to handle incomplete or invalid messages
90
 * and return the corresponding status codes.
91
 *
92
 * The "datalen" parameter must be initialized to the size of the buffer
93
 * pointed to by the "data" parameter.  cupsSideChannelDoRequest() will
94
 * update the value to contain the number of data bytes in the buffer.
95
 *
96
 * @since CUPS 1.3/macOS 10.5@
97
 */
98
99
int         /* O - 0 on success, -1 on error */
100
cupsSideChannelRead(
101
    cups_sc_command_t *command,   /* O - Command code */
102
    cups_sc_status_t  *status,    /* O - Status code */
103
    char              *data,    /* O - Data buffer pointer */
104
    int               *datalen,   /* IO - Size of data buffer on entry, number of bytes in buffer on return */
105
    double            timeout)    /* I  - Timeout in seconds */
106
0
{
107
0
  char    *buffer;    /* Message buffer */
108
0
  ssize_t bytes;      /* Bytes read */
109
0
  int   templen;    /* Data length from message */
110
0
  int   nfds;     /* Number of file descriptors */
111
0
#ifdef HAVE_POLL
112
0
  struct pollfd pfd;      /* Poll structure for poll() */
113
#else /* select() */
114
  fd_set  input_set;    /* Input set for select() */
115
  struct timeval stimeout;    /* Timeout value for select() */
116
#endif /* HAVE_POLL */
117
118
119
0
  DEBUG_printf(("cupsSideChannelRead(command=%p, status=%p, data=%p, "
120
0
                "datalen=%p(%d), timeout=%.3f)", command, status, data,
121
0
    datalen, datalen ? *datalen : -1, timeout));
122
123
 /*
124
  * Range check input...
125
  */
126
127
0
  if (!command || !status)
128
0
    return (-1);
129
130
 /*
131
  * See if we have pending data on the side-channel socket...
132
  */
133
134
0
#ifdef HAVE_POLL
135
0
  pfd.fd     = CUPS_SC_FD;
136
0
  pfd.events = POLLIN;
137
138
0
  while ((nfds = poll(&pfd, 1,
139
0
          timeout < 0.0 ? -1 : (int)(timeout * 1000))) < 0 &&
140
0
   (errno == EINTR || errno == EAGAIN))
141
0
    ;
142
143
#else /* select() */
144
  FD_ZERO(&input_set);
145
  FD_SET(CUPS_SC_FD, &input_set);
146
147
  stimeout.tv_sec  = (int)timeout;
148
  stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
149
150
  while ((nfds = select(CUPS_SC_FD + 1, &input_set, NULL, NULL,
151
      timeout < 0.0 ? NULL : &stimeout)) < 0 &&
152
   (errno == EINTR || errno == EAGAIN))
153
    ;
154
155
#endif /* HAVE_POLL */
156
157
0
  if (nfds < 1)
158
0
  {
159
0
    *command = CUPS_SC_CMD_NONE;
160
0
    *status  = nfds==0 ? CUPS_SC_STATUS_TIMEOUT : CUPS_SC_STATUS_IO_ERROR;
161
0
    return (-1);
162
0
  }
163
164
 /*
165
  * Read a side-channel message for the format:
166
  *
167
  * Byte(s)  Description
168
  * -------  -------------------------------------------
169
  * 0        Command code
170
  * 1        Status code
171
  * 2-3      Data length (network byte order)
172
  * 4-N      Data
173
  */
174
175
0
  if ((buffer = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
176
0
  {
177
0
    *command = CUPS_SC_CMD_NONE;
178
0
    *status  = CUPS_SC_STATUS_TOO_BIG;
179
180
0
    return (-1);
181
0
  }
182
183
0
  while ((bytes = read(CUPS_SC_FD, buffer, _CUPS_SC_MAX_BUFFER)) < 0)
184
0
    if (errno != EINTR && errno != EAGAIN)
185
0
    {
186
0
      DEBUG_printf(("1cupsSideChannelRead: Read error: %s", strerror(errno)));
187
188
0
      _cupsBufferRelease(buffer);
189
190
0
      *command = CUPS_SC_CMD_NONE;
191
0
      *status  = CUPS_SC_STATUS_IO_ERROR;
192
193
0
      return (-1);
194
0
    }
195
196
 /*
197
  * Watch for EOF or too few bytes...
198
  */
199
200
0
  if (bytes < 4)
201
0
  {
202
0
    DEBUG_printf(("1cupsSideChannelRead: Short read of " CUPS_LLFMT " bytes", CUPS_LLCAST bytes));
203
204
0
    _cupsBufferRelease(buffer);
205
206
0
    *command = CUPS_SC_CMD_NONE;
207
0
    *status  = CUPS_SC_STATUS_BAD_MESSAGE;
208
209
0
    return (-1);
210
0
  }
211
212
 /*
213
  * Validate the command code in the message...
214
  */
215
216
0
  if (buffer[0] < CUPS_SC_CMD_SOFT_RESET ||
217
0
      buffer[0] >= CUPS_SC_CMD_MAX)
218
0
  {
219
0
    DEBUG_printf(("1cupsSideChannelRead: Bad command %d!", buffer[0]));
220
221
0
    _cupsBufferRelease(buffer);
222
223
0
    *command = CUPS_SC_CMD_NONE;
224
0
    *status  = CUPS_SC_STATUS_BAD_MESSAGE;
225
226
0
    return (-1);
227
0
  }
228
229
0
  *command = (cups_sc_command_t)buffer[0];
230
231
 /*
232
  * Validate the data length in the message...
233
  */
234
235
0
  templen = ((buffer[2] & 255) << 8) | (buffer[3] & 255);
236
237
0
  if (templen > 0 && (!data || !datalen))
238
0
  {
239
   /*
240
    * Either the response is bigger than the provided buffer or the
241
    * response is bigger than we've read...
242
    */
243
244
0
    *status = CUPS_SC_STATUS_TOO_BIG;
245
0
  }
246
0
  else if (!datalen || templen > *datalen || templen > (bytes - 4))
247
0
  {
248
   /*
249
    * Either the response is bigger than the provided buffer or the
250
    * response is bigger than we've read...
251
    */
252
253
0
    *status = CUPS_SC_STATUS_TOO_BIG;
254
0
  }
255
0
  else
256
0
  {
257
   /*
258
    * The response data will fit, copy it over and provide the actual
259
    * length...
260
    */
261
262
0
    *status  = (cups_sc_status_t)buffer[1];
263
0
    *datalen = templen;
264
265
0
    memcpy(data, buffer + 4, (size_t)templen);
266
0
  }
267
268
0
  _cupsBufferRelease(buffer);
269
270
0
  DEBUG_printf(("1cupsSideChannelRead: Returning status=%d", *status));
271
272
0
  return (0);
273
0
}
274
275
276
/*
277
 * 'cupsSideChannelSNMPGet()' - Query a SNMP OID's value.
278
 *
279
 * This function asks the backend to do a SNMP OID query on behalf of the
280
 * filter, port monitor, or backend using the default community name.
281
 *
282
 * "oid" contains a numeric OID consisting of integers separated by periods,
283
 * for example ".1.3.6.1.2.1.43".  Symbolic names from SNMP MIBs are not
284
 * supported and must be converted to their numeric forms.
285
 *
286
 * On input, "data" and "datalen" provide the location and size of the
287
 * buffer to hold the OID value as a string. HEX-String (binary) values are
288
 * converted to hexadecimal strings representing the binary data, while
289
 * NULL-Value and unknown OID types are returned as the empty string.
290
 * The returned "datalen" does not include the trailing nul.
291
 *
292
 * @code CUPS_SC_STATUS_NOT_IMPLEMENTED@ is returned by backends that do not
293
 * support SNMP queries.  @code CUPS_SC_STATUS_NO_RESPONSE@ is returned when
294
 * the printer does not respond to the SNMP query.
295
 *
296
 * @since CUPS 1.4/macOS 10.6@
297
 */
298
299
cups_sc_status_t      /* O  - Query status */
300
cupsSideChannelSNMPGet(
301
    const char *oid,      /* I  - OID to query */
302
    char       *data,     /* I  - Buffer for OID value */
303
    int        *datalen,    /* IO - Size of OID buffer on entry, size of value on return */
304
    double     timeout)     /* I  - Timeout in seconds */
305
0
{
306
0
  cups_sc_status_t  status;   /* Status of command */
307
0
  cups_sc_command_t rcommand; /* Response command */
308
0
  char      *real_data; /* Real data buffer for response */
309
0
  int     real_datalen, /* Real length of data buffer */
310
0
      real_oidlen;  /* Length of returned OID string */
311
312
313
0
  DEBUG_printf(("cupsSideChannelSNMPGet(oid=\"%s\", data=%p, datalen=%p(%d), "
314
0
                "timeout=%.3f)", oid, data, datalen, datalen ? *datalen : -1,
315
0
    timeout));
316
317
 /*
318
  * Range check input...
319
  */
320
321
0
  if (!oid || !*oid || !data || !datalen || *datalen < 2)
322
0
    return (CUPS_SC_STATUS_BAD_MESSAGE);
323
324
0
  *data = '\0';
325
326
 /*
327
  * Send the request to the backend and wait for a response...
328
  */
329
330
0
  if (cupsSideChannelWrite(CUPS_SC_CMD_SNMP_GET, CUPS_SC_STATUS_NONE, oid,
331
0
                           (int)strlen(oid) + 1, timeout))
332
0
    return (CUPS_SC_STATUS_TIMEOUT);
333
334
0
  if ((real_data = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
335
0
    return (CUPS_SC_STATUS_TOO_BIG);
336
337
0
  real_datalen = _CUPS_SC_MAX_BUFFER;
338
0
  if (cupsSideChannelRead(&rcommand, &status, real_data, &real_datalen, timeout))
339
0
  {
340
0
    _cupsBufferRelease(real_data);
341
0
    return (CUPS_SC_STATUS_TIMEOUT);
342
0
  }
343
344
0
  if (rcommand != CUPS_SC_CMD_SNMP_GET)
345
0
  {
346
0
    _cupsBufferRelease(real_data);
347
0
    return (CUPS_SC_STATUS_BAD_MESSAGE);
348
0
  }
349
350
0
  if (status == CUPS_SC_STATUS_OK)
351
0
  {
352
   /*
353
    * Parse the response of the form "oid\0value"...
354
    */
355
356
0
    real_oidlen  = (int)strlen(real_data) + 1;
357
0
    real_datalen -= real_oidlen;
358
359
0
    if ((real_datalen + 1) > *datalen)
360
0
    {
361
0
      _cupsBufferRelease(real_data);
362
0
      return (CUPS_SC_STATUS_TOO_BIG);
363
0
    }
364
365
0
    memcpy(data, real_data + real_oidlen, (size_t)real_datalen);
366
0
    data[real_datalen] = '\0';
367
368
0
    *datalen = real_datalen;
369
0
  }
370
371
0
  _cupsBufferRelease(real_data);
372
373
0
  return (status);
374
0
}
375
376
377
/*
378
 * 'cupsSideChannelSNMPWalk()' - Query multiple SNMP OID values.
379
 *
380
 * This function asks the backend to do multiple SNMP OID queries on behalf
381
 * of the filter, port monitor, or backend using the default community name.
382
 * All OIDs under the "parent" OID are queried and the results are sent to
383
 * the callback function you provide.
384
 *
385
 * "oid" contains a numeric OID consisting of integers separated by periods,
386
 * for example ".1.3.6.1.2.1.43".  Symbolic names from SNMP MIBs are not
387
 * supported and must be converted to their numeric forms.
388
 *
389
 * "timeout" specifies the timeout for each OID query. The total amount of
390
 * time will depend on the number of OID values found and the time required
391
 * for each query.
392
 *
393
 * "cb" provides a function to call for every value that is found. "context"
394
 * is an application-defined pointer that is sent to the callback function
395
 * along with the OID and current data. The data passed to the callback is the
396
 * same as returned by @link cupsSideChannelSNMPGet@.
397
 *
398
 * @code CUPS_SC_STATUS_NOT_IMPLEMENTED@ is returned by backends that do not
399
 * support SNMP queries.  @code CUPS_SC_STATUS_NO_RESPONSE@ is returned when
400
 * the printer does not respond to the first SNMP query.
401
 *
402
 * @since CUPS 1.4/macOS 10.6@
403
 */
404
405
cups_sc_status_t      /* O - Status of first query of @code CUPS_SC_STATUS_OK@ on success */
406
cupsSideChannelSNMPWalk(
407
    const char          *oid,   /* I - First numeric OID to query */
408
    double              timeout,  /* I - Timeout for each query in seconds */
409
    cups_sc_walk_func_t cb,   /* I - Function to call with each value */
410
    void                *context) /* I - Application-defined pointer to send to callback */
411
0
{
412
0
  cups_sc_status_t  status;   /* Status of command */
413
0
  cups_sc_command_t rcommand; /* Response command */
414
0
  char      *real_data; /* Real data buffer for response */
415
0
  int     real_datalen; /* Real length of data buffer */
416
0
  size_t    real_oidlen,  /* Length of returned OID string */
417
0
      oidlen;   /* Length of first OID */
418
0
  const char    *current_oid; /* Current OID */
419
0
  char      last_oid[2048]; /* Last OID */
420
421
422
0
  DEBUG_printf(("cupsSideChannelSNMPWalk(oid=\"%s\", timeout=%.3f, cb=%p, "
423
0
                "context=%p)", oid, timeout, cb, context));
424
425
 /*
426
  * Range check input...
427
  */
428
429
0
  if (!oid || !*oid || !cb)
430
0
    return (CUPS_SC_STATUS_BAD_MESSAGE);
431
432
0
  if ((real_data = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
433
0
    return (CUPS_SC_STATUS_TOO_BIG);
434
435
 /*
436
  * Loop until the OIDs don't match...
437
  */
438
439
0
  current_oid = oid;
440
0
  oidlen      = strlen(oid);
441
0
  last_oid[0] = '\0';
442
443
0
  do
444
0
  {
445
   /*
446
    * Send the request to the backend and wait for a response...
447
    */
448
449
0
    if (cupsSideChannelWrite(CUPS_SC_CMD_SNMP_GET_NEXT, CUPS_SC_STATUS_NONE,
450
0
                             current_oid, (int)strlen(current_oid) + 1, timeout))
451
0
    {
452
0
      _cupsBufferRelease(real_data);
453
0
      return (CUPS_SC_STATUS_TIMEOUT);
454
0
    }
455
456
0
    real_datalen = _CUPS_SC_MAX_BUFFER;
457
0
    if (cupsSideChannelRead(&rcommand, &status, real_data, &real_datalen,
458
0
                            timeout))
459
0
    {
460
0
      _cupsBufferRelease(real_data);
461
0
      return (CUPS_SC_STATUS_TIMEOUT);
462
0
    }
463
464
0
    if (rcommand != CUPS_SC_CMD_SNMP_GET_NEXT)
465
0
    {
466
0
      _cupsBufferRelease(real_data);
467
0
      return (CUPS_SC_STATUS_BAD_MESSAGE);
468
0
    }
469
470
0
    if (status == CUPS_SC_STATUS_OK)
471
0
    {
472
     /*
473
      * Parse the response of the form "oid\0value"...
474
      */
475
476
0
      if (strncmp(real_data, oid, oidlen) || real_data[oidlen] != '.' ||
477
0
          !strcmp(real_data, last_oid))
478
0
      {
479
       /*
480
        * Done with this set of OIDs...
481
  */
482
483
0
  _cupsBufferRelease(real_data);
484
0
        return (CUPS_SC_STATUS_OK);
485
0
      }
486
487
0
      if ((size_t)real_datalen < sizeof(real_data))
488
0
        real_data[real_datalen] = '\0';
489
490
0
      real_oidlen  = strlen(real_data) + 1;
491
0
      real_datalen -= (int)real_oidlen;
492
493
     /*
494
      * Call the callback with the OID and data...
495
      */
496
497
0
      (*cb)(real_data, real_data + real_oidlen, real_datalen, context);
498
499
     /*
500
      * Update the current OID...
501
      */
502
503
0
      current_oid = real_data;
504
0
      strlcpy(last_oid, current_oid, sizeof(last_oid));
505
0
    }
506
0
  }
507
0
  while (status == CUPS_SC_STATUS_OK);
508
509
0
  _cupsBufferRelease(real_data);
510
511
0
  return (status);
512
0
}
513
514
515
/*
516
 * 'cupsSideChannelWrite()' - Write a side-channel message.
517
 *
518
 * This function is normally only called by backend programs to send
519
 * responses to a filter, driver, or port monitor program.
520
 *
521
 * @since CUPS 1.3/macOS 10.5@
522
 */
523
524
int         /* O - 0 on success, -1 on error */
525
cupsSideChannelWrite(
526
    cups_sc_command_t command,    /* I - Command code */
527
    cups_sc_status_t  status,   /* I - Status code */
528
    const char        *data,    /* I - Data buffer pointer */
529
    int               datalen,    /* I - Number of bytes of data */
530
    double            timeout)    /* I - Timeout in seconds */
531
0
{
532
0
  char    *buffer;    /* Message buffer */
533
0
  ssize_t bytes;      /* Bytes written */
534
0
#ifdef HAVE_POLL
535
0
  struct pollfd pfd;      /* Poll structure for poll() */
536
#else /* select() */
537
  fd_set  output_set;   /* Output set for select() */
538
  struct timeval stimeout;    /* Timeout value for select() */
539
#endif /* HAVE_POLL */
540
541
542
 /*
543
  * Range check input...
544
  */
545
546
0
  if (command < CUPS_SC_CMD_SOFT_RESET || command >= CUPS_SC_CMD_MAX ||
547
0
      datalen < 0 || datalen > _CUPS_SC_MAX_DATA || (datalen > 0 && !data))
548
0
    return (-1);
549
550
 /*
551
  * See if we can safely write to the side-channel socket...
552
  */
553
554
0
#ifdef HAVE_POLL
555
0
  pfd.fd     = CUPS_SC_FD;
556
0
  pfd.events = POLLOUT;
557
558
0
  if (timeout < 0.0)
559
0
  {
560
0
    if (poll(&pfd, 1, -1) < 1)
561
0
      return (-1);
562
0
  }
563
0
  else if (poll(&pfd, 1, (int)(timeout * 1000)) < 1)
564
0
    return (-1);
565
566
#else /* select() */
567
  FD_ZERO(&output_set);
568
  FD_SET(CUPS_SC_FD, &output_set);
569
570
  if (timeout < 0.0)
571
  {
572
    if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, NULL) < 1)
573
      return (-1);
574
  }
575
  else
576
  {
577
    stimeout.tv_sec  = (int)timeout;
578
    stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
579
580
    if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, &stimeout) < 1)
581
      return (-1);
582
  }
583
#endif /* HAVE_POLL */
584
585
 /*
586
  * Write a side-channel message in the format:
587
  *
588
  * Byte(s)  Description
589
  * -------  -------------------------------------------
590
  * 0        Command code
591
  * 1        Status code
592
  * 2-3      Data length (network byte order) <= 16384
593
  * 4-N      Data
594
  */
595
596
0
  if ((buffer = _cupsBufferGet((size_t)datalen + 4)) == NULL)
597
0
    return (-1);
598
599
0
  buffer[0] = command;
600
0
  buffer[1] = status;
601
0
  buffer[2] = (char)(datalen >> 8);
602
0
  buffer[3] = (char)(datalen & 255);
603
604
0
  bytes = 4;
605
606
0
  if (datalen > 0)
607
0
  {
608
0
    memcpy(buffer + 4, data, (size_t)datalen);
609
0
    bytes += datalen;
610
0
  }
611
612
0
  while (write(CUPS_SC_FD, buffer, (size_t)bytes) < 0)
613
0
    if (errno != EINTR && errno != EAGAIN)
614
0
    {
615
0
      _cupsBufferRelease(buffer);
616
0
      return (-1);
617
0
    }
618
619
0
  _cupsBufferRelease(buffer);
620
621
0
  return (0);
622
0
}