Coverage Report

Created: 2026-07-15 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dropbear/src/svr-authpubkeyoptions.c
Line
Count
Source
1
/*
2
 * Dropbear - a SSH2 server
3
 * 
4
 * Copyright (c) 2008 Frederic Moulins
5
 * All rights reserved.
6
 * 
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 * 
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 * 
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE. 
24
 *
25
 * This file incorporates work covered by the following copyright and  
26
 * permission notice:
27
 *
28
 *  Author: Tatu Ylonen <ylo@cs.hut.fi>
29
 *  Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
30
 *                All rights reserved
31
 *  As far as I am concerned, the code I have written for this software
32
 *  can be used freely for any purpose.  Any derived versions of this
33
 *  software must be clearly marked as such, and if the derived work is
34
 *  incompatible with the protocol description in the RFC file, it must be
35
 *  called by a name other than "ssh" or "Secure Shell".
36
 *
37
 * This copyright and permission notice applies to the code parsing public keys
38
 * options string which can also be found in OpenSSH auth-options.c file 
39
 * (auth_parse_options).
40
 *
41
 */
42
43
/* Process pubkey options during a pubkey auth request */
44
#include "includes.h"
45
#include "session.h"
46
#include "dbutil.h"
47
#include "signkey.h"
48
#include "auth.h"
49
#include "runopts.h"
50
51
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
52
53
/* Returns 1 if pubkey allows agent forwarding,
54
 * 0 otherwise */
55
0
int svr_pubkey_allows_agentfwd() {
56
0
  if (ses.authstate.pubkey_options 
57
0
    && ses.authstate.pubkey_options->no_agent_forwarding_flag) {
58
0
    return 0;
59
0
  }
60
0
  return 1;
61
0
}
62
63
/* Returns 1 if pubkey allows tcp forwarding,
64
 * 0 otherwise */
65
0
int svr_pubkey_allows_tcpfwd() {
66
0
  if (ses.authstate.pubkey_options 
67
0
    && ses.authstate.pubkey_options->no_port_forwarding_flag) {
68
0
    return 0;
69
0
  }
70
0
  return 1;
71
0
}
72
73
/* Returns 1 if a forced command pubkey option is set */
74
0
int svr_pubkey_has_forced_command(void) {
75
0
  return ses.authstate.pubkey_options
76
0
    && ses.authstate.pubkey_options->forced_command;
77
0
}
78
79
/* Returns 1 if pubkey allows x11 forwarding,
80
 * 0 otherwise */
81
0
int svr_pubkey_allows_x11fwd() {
82
0
  if (ses.authstate.pubkey_options 
83
0
    && ses.authstate.pubkey_options->no_x11_forwarding_flag) {
84
0
    return 0;
85
0
  }
86
0
  return 1;
87
0
}
88
89
/* Returns 1 if pubkey allows pty, 0 otherwise */
90
0
int svr_pubkey_allows_pty() {
91
0
  if (ses.authstate.pubkey_options 
92
0
    && ses.authstate.pubkey_options->no_pty_flag) {
93
0
    return 0;
94
0
  }
95
0
  return 1;
96
0
}
97
98
/* Returns 1 if pubkey allows local tcp forwarding to the provided destination,
99
 * 0 otherwise */
100
0
int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) {
101
0
  if (ses.authstate.pubkey_options
102
0
    && ses.authstate.pubkey_options->permit_open_destinations) {
103
0
    m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first;
104
0
    while (iter) {
105
0
      struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item;
106
0
      if (strcmp(entry->host, host) == 0) {
107
0
        if ((entry->port == PUBKEY_OPTIONS_ANY_PORT) || (entry->port == port)) {
108
0
          return 1;
109
0
        }
110
0
      }
111
112
0
      iter = iter->next;
113
0
    }
114
115
0
    return 0;
116
0
  }
117
118
0
  return 1;
119
0
}
120
121
/* Returns 1 if pubkey allows remote tcp forwarding from the provided source,
122
 * 0 otherwise */
123
0
int svr_pubkey_allows_remote_tcpfwd(const char *host, unsigned int port) {
124
  /* no host restrictions */
125
0
  (void)host;
126
127
0
  if (ses.authstate.pubkey_options
128
0
    && ses.authstate.pubkey_options->permit_listens) {
129
0
    m_list_elem *iter = ses.authstate.pubkey_options->permit_listens->first;
130
0
    while (iter) {
131
0
      struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item;
132
0
      if (entry->port == port) {
133
0
        return 1;
134
0
      }
135
136
0
      iter = iter->next;
137
0
    }
138
139
0
    return 0;
140
0
  }
141
142
0
  return 1;
143
0
}
144
145
/* Set chansession command to the one forced
146
 * by any 'command' public key option. */
147
0
void svr_pubkey_set_forced_command(struct ChanSess *chansess) {
148
0
  if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->forced_command) {
149
0
    TRACE(("Forced command '%s'", ses.authstate.pubkey_options->forced_command))
150
0
    if (chansess->cmd) {
151
      /* original_command takes ownership */
152
0
      chansess->original_command = chansess->cmd;
153
0
      chansess->cmd = NULL;
154
0
    } else {
155
0
      chansess->original_command = m_strdup("");
156
0
    }
157
0
    chansess->cmd = m_strdup(ses.authstate.pubkey_options->forced_command);
158
#if LOG_COMMANDS
159
    dropbear_log(LOG_INFO, "Command forced to '%s'", chansess->original_command);
160
#endif
161
0
  }
162
0
}
163
164
/* Free potential public key options */
165
0
void svr_pubkey_options_cleanup(struct PubKeyOptions *pubkey_options) {
166
0
  if (pubkey_options) {
167
0
    m_free(pubkey_options->forced_command);
168
0
    if (pubkey_options->permit_open_destinations) {
169
0
      m_list_elem *iter = pubkey_options->permit_open_destinations->first;
170
0
      while (iter) {
171
0
        struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter);
172
0
        m_free(entry->host);
173
0
        m_free(entry);
174
0
        iter = pubkey_options->permit_open_destinations->first;
175
0
      }
176
0
      m_free(pubkey_options->permit_open_destinations);
177
0
    }
178
0
    if (pubkey_options->permit_listens) {
179
0
      m_list_elem *iter = pubkey_options->permit_listens->first;
180
0
      while (iter) {
181
0
        struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter);
182
0
        m_free(entry->host);
183
0
        m_free(entry);
184
0
        iter = pubkey_options->permit_listens->first;
185
0
      }
186
0
      m_free(pubkey_options->permit_listens);
187
0
    }
188
0
    m_free(pubkey_options->info_env);
189
0
    m_free(pubkey_options);
190
0
  }
191
0
}
192
193
/* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched,
194
   and increments the options_buf */
195
0
static int match_option(buffer *options_buf, const char *opt_name) {
196
0
  const unsigned int len = strlen(opt_name);
197
0
  if (options_buf->len - options_buf->pos < len) {
198
0
    return DROPBEAR_FAILURE;
199
0
  }
200
0
  if (strncasecmp((const char *) buf_getptr(options_buf, len), opt_name, len) == 0) {
201
0
    buf_incrpos(options_buf, len);
202
0
    return DROPBEAR_SUCCESS;
203
0
  }
204
0
  return DROPBEAR_FAILURE;
205
0
}
206
207
/* Parse pubkey options and return a malloced struct.
208
 * options_buf may be NULL, treated as empty.
209
 * Returns NULL on failure */
210
struct PubKeyOptions*
211
0
svr_parse_pubkey_options(buffer *options_buf, int line_num, const char* filename) {
212
0
  struct PubKeyOptions *pubkey_options = NULL;
213
214
0
  TRACE(("enter addpubkeyoptions"))
215
216
0
  pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions ));
217
218
0
  if (!options_buf) {
219
0
    TRACE(("leave addpubkeyoptions, null options"))
220
0
    return pubkey_options;
221
0
  }
222
223
0
  buf_setpos(options_buf, 0);
224
0
  while (options_buf->pos < options_buf->len) {
225
0
    if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) {
226
0
      dropbear_log(LOG_WARNING, "Port forwarding disabled.");
227
0
      pubkey_options->no_port_forwarding_flag = 1;
228
0
      goto next_option;
229
0
    }
230
0
    if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) {
231
0
#if DROPBEAR_SVR_AGENTFWD
232
0
      dropbear_log(LOG_WARNING, "Agent forwarding disabled.");
233
0
      pubkey_options->no_agent_forwarding_flag = 1;
234
0
#endif
235
0
      goto next_option;
236
0
    }
237
0
    if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) {
238
#if DROPBEAR_X11FWD
239
      dropbear_log(LOG_WARNING, "X11 forwarding disabled.");
240
      pubkey_options->no_x11_forwarding_flag = 1;
241
#endif
242
0
      goto next_option;
243
0
    }
244
0
    if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) {
245
0
      dropbear_log(LOG_WARNING, "Pty allocation disabled.");
246
0
      pubkey_options->no_pty_flag = 1;
247
0
      goto next_option;
248
0
    }
249
0
    if (match_option(options_buf, "restrict") == DROPBEAR_SUCCESS) {
250
0
      dropbear_log(LOG_WARNING, "Restrict option set");
251
0
      pubkey_options->no_port_forwarding_flag = 1;
252
0
#if DROPBEAR_SVR_AGENTFWD
253
0
      pubkey_options->no_agent_forwarding_flag = 1;
254
0
#endif
255
#if DROPBEAR_X11FWD
256
      pubkey_options->no_x11_forwarding_flag = 1;
257
#endif
258
0
      pubkey_options->no_pty_flag = 1;
259
0
      goto next_option;
260
0
    }
261
0
    if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) {
262
0
      int escaped = 0;
263
0
      const unsigned char* command_start = buf_getptr(options_buf, 0);
264
265
0
      if (pubkey_options->forced_command) {
266
        /* multiple command= options */
267
0
        goto bad_option;
268
0
      }
269
270
0
      while (options_buf->pos < options_buf->len) {
271
0
        const char c = buf_getbyte(options_buf);
272
0
        if (!escaped && c == '"') {
273
0
          const int command_len = buf_getptr(options_buf, 0) - command_start;
274
0
          pubkey_options->forced_command = m_malloc(command_len);
275
0
          memcpy(pubkey_options->forced_command,
276
0
              command_start, command_len-1);
277
0
          pubkey_options->forced_command[command_len-1] = '\0';
278
0
          goto next_option;
279
0
        }
280
0
        escaped = (!escaped && c == '\\');
281
0
      }
282
0
      dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option");
283
0
      goto bad_option;
284
0
    }
285
286
0
    if (match_option(options_buf, "permitopen=\"") == DROPBEAR_SUCCESS) {
287
0
      int valid_option = 0;
288
0
      const unsigned char* permitopen_start = buf_getptr(options_buf, 0);
289
290
0
      if (!pubkey_options->permit_open_destinations) {
291
0
        pubkey_options->permit_open_destinations = list_new();
292
0
      }
293
294
0
      while (options_buf->pos < options_buf->len) {
295
0
        const char c = buf_getbyte(options_buf);
296
0
        if (c == '"') {
297
0
          char *spec = NULL;
298
0
          char *portstring = NULL;
299
0
          const int permitopen_len = buf_getptr(options_buf, 0) - permitopen_start;
300
0
          struct PermitTCPFwdEntry *entry =
301
0
              (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry));
302
303
0
          list_append(pubkey_options->permit_open_destinations, entry);
304
0
          spec = m_malloc(permitopen_len);
305
0
          memcpy(spec, permitopen_start, permitopen_len - 1);
306
0
          spec[permitopen_len - 1] = '\0';
307
0
          if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS)
308
0
            && entry->host && portstring) {
309
0
            if (strcmp(portstring, "*") == 0) {
310
0
              valid_option = 1;
311
0
              entry->port = PUBKEY_OPTIONS_ANY_PORT;
312
0
              TRACE(("local port forwarding allowed to host '%s'", entry->host));
313
0
            } else if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS
314
0
              && entry->port <= 65535) {
315
0
              valid_option = 1;
316
0
              TRACE(("local port forwarding allowed to host '%s' and port '%u'",
317
0
                  entry->host, entry->port));
318
0
            }
319
0
          }
320
321
0
          m_free(spec);
322
0
          m_free(portstring);
323
0
          break;
324
0
        }
325
0
      }
326
327
0
      if (valid_option) {
328
0
        goto next_option;
329
0
      } else {
330
0
        dropbear_log(LOG_WARNING, "Badly formatted permitopen= authorized_keys option");
331
0
        goto bad_option;
332
0
      }
333
0
    }
334
335
0
    if (match_option(options_buf, "permitlisten=\"") == DROPBEAR_SUCCESS) {
336
0
      int valid_option = 0;
337
0
      const unsigned char* permitlisten_start = buf_getptr(options_buf, 0);
338
339
0
      if (!pubkey_options->permit_listens) {
340
0
        pubkey_options->permit_listens = list_new();
341
0
      }
342
343
0
      while (options_buf->pos < options_buf->len) {
344
0
        const char c = buf_getbyte(options_buf);
345
0
        if (c == '"') {
346
0
          char *spec = NULL;
347
0
          const int permitlisten_len = buf_getptr(options_buf, 0) - permitlisten_start;
348
0
          struct PermitTCPFwdEntry *entry =
349
0
              (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry));
350
351
0
          list_append(pubkey_options->permit_listens, entry);
352
          /* permitlisten_len includes trailing '"' */
353
0
          spec = m_malloc(permitlisten_len);
354
0
          memcpy(spec, permitlisten_start, permitlisten_len - 1);
355
0
          spec[permitlisten_len - 1] = '\0';
356
357
          /* Only a plain port accepted.
358
           * OpenSSH supports [host:]port, but that isn't implemented.
359
           * port="*" isn't supported either, since it only is useful
360
           * with a host: part. */
361
362
0
          if (m_str_to_uint(spec, &entry->port) == DROPBEAR_SUCCESS) {
363
0
            valid_option = 1;
364
0
            TRACE(("remote forwarding allows listening on port %u",
365
0
                entry->port));
366
0
          }
367
368
0
          m_free(spec);
369
0
          break;
370
0
        }
371
0
      }
372
373
0
      if (valid_option) {
374
0
        goto next_option;
375
0
      } else {
376
0
        dropbear_log(LOG_WARNING, "Badly formatted permitlisten= authorized_keys option");
377
0
        goto bad_option;
378
0
      }
379
0
    }
380
381
0
    if (match_option(options_buf, "no-touch-required") == DROPBEAR_SUCCESS) {
382
0
#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519
383
0
      dropbear_log(LOG_WARNING, "No user presence check required for U2F/FIDO key.");
384
0
      pubkey_options->no_touch_required_flag = 1;
385
0
#endif
386
0
      goto next_option;
387
0
    }
388
0
    if (match_option(options_buf, "verify-required") == DROPBEAR_SUCCESS) {
389
0
#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519
390
0
      dropbear_log(LOG_WARNING, "User verification required for U2F/FIDO key.");
391
0
      pubkey_options->verify_required_flag = 1;
392
0
#endif
393
0
      goto next_option;
394
0
    }
395
396
0
next_option:
397
    /*
398
     * Skip the comma, and move to the next option
399
     * (or break out if there are no more).
400
     */
401
0
    if (options_buf->pos < options_buf->len 
402
0
        && buf_getbyte(options_buf) != ',') {
403
0
      goto bad_option;
404
0
    }
405
    /* Process the next option. */
406
0
  }
407
  /* parsed all options with no problem */
408
0
  goto end;
409
410
0
bad_option:
411
0
  svr_pubkey_options_cleanup(pubkey_options);
412
0
  pubkey_options = NULL;
413
0
  dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num);
414
415
0
end:
416
0
  TRACE(("leave addpubkeyoptions"))
417
0
  return pubkey_options;
418
0
}
419
420
#endif