# show up as GPFs at the point of failure rather than appearing
# later on as second-level damage.
#
+# - XFLAGS=/DFUZZING
+# Builds a version of PuTTY with some tweaks to make fuzz testing
+# easier: the SSH random number generator is replaced by one that
+# always returns the same thing. Note that this makes SSH
+# completely insecure -- a FUZZING build should never be used to
+# connect to a real server.
!end
# ------------------------------------------------------------
PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH
+ ux_x11 uxpty uxsignal testback putty.icns info.plist
+
+fuzzterm : [U] UXTERM CHARSET misc uxmisc uxucs fuzzterm time settings
+ + uxstore be_none
--- /dev/null
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define PUTTY_DO_GLOBALS
+#include "putty.h"
+#include "terminal.h"
+
+int main(int argc, char **argv)
+{
+ char blk[512];
+ size_t len;
+ Terminal *term;
+ Conf *conf;
+ struct unicode_data ucsdata;
+
+ conf = conf_new();
+ do_defaults(NULL, conf);
+ init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage),
+ conf_get_int(conf, CONF_utf8_override),
+ CS_NONE, conf_get_int(conf, CONF_vtmode));
+
+ term = term_init(conf, &ucsdata, NULL);
+ term_size(term, 24, 80, 10000);
+ term->ldisc = NULL;
+ /* Tell american fuzzy lop that this is a good place to fork. */
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ __AFL_INIT();
+#endif
+ while (!feof(stdin)) {
+ len = fread(blk, 1, sizeof(blk), stdin);
+ term_data(term, 0, blk, len);
+ }
+ term_update(term);
+ return 0;
+}
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{ return 0; }
+
+/* functions required by terminal.c */
+
+void request_resize(void *frontend, int x, int y) { }
+void do_text(Context ctx, int x, int y, wchar_t * text, int len,
+ unsigned long attr, int lattr)
+{
+ int i;
+
+ printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y);
+ for (i = 0; i < len; i++) {
+ printf(" %x", (unsigned)text[i]);
+ }
+ printf("\n");
+}
+void do_cursor(Context ctx, int x, int y, wchar_t * text, int len,
+ unsigned long attr, int lattr)
+{
+ int i;
+
+ printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y);
+ for (i = 0; i < len; i++) {
+ printf(" %x", (unsigned)text[i]);
+ }
+ printf("\n");
+}
+int char_width(Context ctx, int uc) { return 1; }
+void set_title(void *frontend, char *t) { }
+void set_icon(void *frontend, char *t) { }
+void set_sbar(void *frontend, int a, int b, int c) { }
+
+void ldisc_send(void *handle, const char *buf, int len, int interactive) {}
+void ldisc_echoedit_update(void *handle) {}
+Context get_ctx(void *frontend) {
+ static char x;
+
+ return &x;
+}
+void free_ctx(Context ctx) { }
+void palette_set(void *frontend, int a, int b, int c, int d) { }
+void palette_reset(void *frontend) { }
+void write_clip(void *frontend, wchar_t *a, int *b, int c, int d) { }
+void get_clip(void *frontend, wchar_t **w, int *i) { }
+void set_raw_mouse_mode(void *frontend, int m) { }
+void request_paste(void *frontend) { }
+void do_beep(void *frontend, int a) { }
+void sys_cursor(void *frontend, int x, int y) { }
+void fatalbox(const char *fmt, ...) { exit(0); }
+void modalfatalbox(const char *fmt, ...) { exit(0); }
+void nonfatal(const char *fmt, ...) { }
+
+void set_iconic(void *frontend, int iconic) { }
+void move_window(void *frontend, int x, int y) { }
+void set_zorder(void *frontend, int top) { }
+void refresh_window(void *frontend) { }
+void set_zoomed(void *frontend, int zoomed) { }
+int is_iconic(void *frontend) { return 0; }
+void get_window_pos(void *frontend, int *x, int *y) { *x = 0; *y = 0; }
+void get_window_pixels(void *frontend, int *x, int *y) { *x = 0; *y = 0; }
+char *get_window_title(void *frontend, int icon) { return "moo"; }
+
+/* needed by timing.c */
+void timer_change_notify(unsigned long next) { }
+
+/* needed by config.c and sercfg.c */
+
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) { }
+int dlg_radiobutton_get(union control *ctrl, void *dlg) { return 0; }
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) { }
+int dlg_checkbox_get(union control *ctrl, void *dlg) { return 0; }
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) { }
+char *dlg_editbox_get(union control *ctrl, void *dlg) { return dupstr("moo"); }
+void dlg_listbox_clear(union control *ctrl, void *dlg) { }
+void dlg_listbox_del(union control *ctrl, void *dlg, int index) { }
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) { }
+void dlg_listbox_addwithid(union control *ctrl, void *dlg,
+ char const *text, int id) { }
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index) { return 0; }
+int dlg_listbox_index(union control *ctrl, void *dlg) { return -1; }
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index) { return 0; }
+void dlg_listbox_select(union control *ctrl, void *dlg, int index) { }
+void dlg_text_set(union control *ctrl, void *dlg, char const *text) { }
+void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) { }
+Filename *dlg_filesel_get(union control *ctrl, void *dlg) { return NULL; }
+void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fn) { }
+FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) { return NULL; }
+void dlg_update_start(union control *ctrl, void *dlg) { }
+void dlg_update_done(union control *ctrl, void *dlg) { }
+void dlg_set_focus(union control *ctrl, void *dlg) { }
+void dlg_label_change(union control *ctrl, void *dlg, char const *text) { }
+union control *dlg_last_focused(union control *ctrl, void *dlg) { return NULL; }
+void dlg_beep(void *dlg) { }
+void dlg_error_msg(void *dlg, const char *msg) { }
+void dlg_end(void *dlg, int value) { }
+void dlg_coloursel_start(union control *ctrl, void *dlg,
+ int r, int g, int b) { }
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+ int *r, int *g, int *b) { return 0; }
+void dlg_refresh(union control *ctrl, void *dlg) { }
+
+/* miscellany */
+void logevent(void *frontend, const char *msg) { }
+int askappend(void *frontend, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx) { return 0; }
+
+const char *const appname = "FuZZterm";
+const int ngsslibs = 0;
+const char *const gsslibnames[0] = { };
+const struct keyvalwhere gsslibkeywords[0] = { };
+
+/*
+ * Default settings that are specific to Unix plink.
+ */
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "TermType"))
+ return dupstr(getenv("TERM"));
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("/dev/ttyS0");
+ return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ if (!strcmp(name, "LogFileName"))
+ return filename_from_str("putty.log");
+ else
+ return filename_from_str("");
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+
+
void *get_ssh_string(int *datalen, const void **data, int *stringlen)
{
void *ret;
- int len;
+ unsigned int len;
if (*datalen < 4)
return NULL;
* Proxy types.
*/
PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
- PROXY_HTTP, PROXY_TELNET, PROXY_CMD
+ PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_FUZZ
};
enum {
}
ssh_fix_verstring(verstring + strlen(protoname));
+#ifdef FUZZING
+ /* FUZZING make PuTTY insecure, so make live use difficult. */
+ verstring[0] = 'I';
+#endif
if (ssh->version == 2) {
size_t len;
"rsa", keystr, fingerprint,
ssh_dialog_callback, ssh);
sfree(keystr);
+#ifdef FUZZING
+ s->dlgret = 1;
+#endif
if (s->dlgret < 0) {
do {
crReturn(0);
/* List encryption algorithms (client->server then server->client). */
for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
warn = FALSE;
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(s->kexlists[k], "none");
+ alg->u.cipher.cipher = NULL;
+ alg->u.cipher.warn = warn;
+#endif /* FUZZING */
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) warn = TRUE;
}
/* List MAC algorithms (client->server then server->client). */
for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(s->kexlists[j], "none");
+ alg->u.mac.mac = NULL;
+ alg->u.mac.etm = FALSE;
+#endif /* FUZZING */
for (i = 0; i < s->nmacs; i++) {
alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name);
alg->u.mac.mac = s->maclist[i];
{
int csbits, scbits;
- csbits = s->cscipher_tobe->real_keybits;
- scbits = s->sccipher_tobe->real_keybits;
+ csbits = s->cscipher_tobe ? s->cscipher_tobe->real_keybits : 0;
+ scbits = s->sccipher_tobe ? s->sccipher_tobe->real_keybits : 0;
s->nbits = (csbits > scbits ? csbits : scbits);
}
/* The keys only have hlen-bit entropy, since they're based on
dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
#endif
- if (!s->hkey ||
- !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+ if (!s->hkey) {
+ bombout(("Server's host key is invalid"));
+ crStopV;
+ }
+
+ if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
(char *)s->exchange_hash,
ssh->kex->hash->hlen)) {
+#ifndef FUZZING
bombout(("Server's host key did not match the signature supplied"));
- crStopV;
+ crStopV;f
+#endif
}
s->keystr = ssh->hostkey->fmtkey(s->hkey);
ssh->hostkey->keytype, s->keystr,
s->fingerprint,
ssh_dialog_callback, ssh);
+#ifdef FUZZING
+ s->dlgret = 1;
+#endif
if (s->dlgret < 0) {
do {
crReturnV;
* the one we saw before.
*/
if (strcmp(ssh->hostkey_str, s->keystr)) {
+#ifndef FUZZING
bombout(("Host key was different in repeat key exchange"));
crStopV;
+#endif
}
sfree(s->keystr);
}
if (ssh->cs_cipher_ctx)
ssh->cscipher->free_context(ssh->cs_cipher_ctx);
ssh->cscipher = s->cscipher_tobe;
- ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+ if (ssh->cscipher) ssh->cs_cipher_ctx = ssh->cscipher->make_context();
if (ssh->cs_mac_ctx)
ssh->csmac->free_context(ssh->cs_mac_ctx);
ssh->csmac = s->csmac_tobe;
ssh->csmac_etm = s->csmac_etm_tobe;
- ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
+ if (ssh->csmac)
+ ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
if (ssh->cs_comp_ctx)
ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
* Set IVs on client-to-server keys. Here we use the exchange
* hash from the _first_ key exchange.
*/
- {
+ if (ssh->cscipher) {
unsigned char *key;
key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C',
ssh->cscipher->setiv(ssh->cs_cipher_ctx, key);
smemclr(key, ssh->cscipher->blksize);
sfree(key);
+ }
+ if (ssh->csmac) {
+ unsigned char *key;
key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E',
ssh->csmac->keylen);
sfree(key);
}
- logeventf(ssh, "Initialised %.200s client->server encryption",
- ssh->cscipher->text_name);
- logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
- ssh->csmac->text_name,
- ssh->csmac_etm ? " (in ETM mode)" : "",
- ssh->cscipher->required_mac ? " (required by cipher)" : "");
+ if (ssh->cscipher)
+ logeventf(ssh, "Initialised %.200s client->server encryption",
+ ssh->cscipher->text_name);
+ if (ssh->csmac)
+ logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
+ ssh->csmac->text_name,
+ ssh->csmac_etm ? " (in ETM mode)" : "",
+ ssh->cscipher->required_mac ? " (required by cipher)" : "");
if (ssh->cscomp->text_name)
logeventf(ssh, "Initialised %s compression",
ssh->cscomp->text_name);
*/
if (ssh->sc_cipher_ctx)
ssh->sccipher->free_context(ssh->sc_cipher_ctx);
- ssh->sccipher = s->sccipher_tobe;
- ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+ if (ssh->sccipher) {
+ ssh->sccipher = s->sccipher_tobe;
+ ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+ }
if (ssh->sc_mac_ctx)
ssh->scmac->free_context(ssh->sc_mac_ctx);
- ssh->scmac = s->scmac_tobe;
- ssh->scmac_etm = s->scmac_etm_tobe;
- ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+ if (ssh->scmac) {
+ ssh->scmac = s->scmac_tobe;
+ ssh->scmac_etm = s->scmac_etm_tobe;
+ ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+ }
if (ssh->sc_comp_ctx)
ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
* Set IVs on server-to-client keys. Here we use the exchange
* hash from the _first_ key exchange.
*/
- {
+ if (ssh->sccipher) {
unsigned char *key;
key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D',
ssh->sccipher->setiv(ssh->sc_cipher_ctx, key);
smemclr(key, ssh->sccipher->blksize);
sfree(key);
+ }
+ if (ssh->scmac) {
+ unsigned char *key;
key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F',
ssh->scmac->keylen);
smemclr(key, ssh->scmac->keylen);
sfree(key);
}
- logeventf(ssh, "Initialised %.200s server->client encryption",
- ssh->sccipher->text_name);
- logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
- ssh->scmac->text_name,
- ssh->scmac_etm ? " (in ETM mode)" : "",
- ssh->sccipher->required_mac ? " (required by cipher)" : "");
+ if (ssh->sccipher)
+ logeventf(ssh, "Initialised %.200s server->client encryption",
+ ssh->sccipher->text_name);
+ if (ssh->scmac)
+ logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
+ ssh->scmac->text_name,
+ ssh->scmac_etm ? " (in ETM mode)" : "",
+ ssh->sccipher->required_mac ? " (required by cipher)" : "");
if (ssh->sccomp->text_name)
logeventf(ssh, "Initialised %s decompression",
ssh->sccomp->text_name);
(BignumInt)byte << (8*i % BIGNUM_INT_BITS);
}
- while (result[0] > 1 && result[result[0]] == 0)
- result[0]--;
+ bn_restore_invariant(result);
return result;
}
(BignumInt)byte << (8*i % BIGNUM_INT_BITS);
}
- while (result[0] > 1 && result[result[0]] == 0)
- result[0]--;
+ bn_restore_invariant(result);
return result;
}
*/
void bignum_set_bit(Bignum bn, int bitnum, int value)
{
- if (bitnum < 0 || bitnum >= (int)(BIGNUM_INT_BITS * bn[0]))
- abort(); /* beyond the end */
- else {
+ if (bitnum < 0 || bitnum >= (int)(BIGNUM_INT_BITS * bn[0])) {
+ if (value) abort(); /* beyond the end */
+ } else {
int v = bitnum / BIGNUM_INT_BITS + 1;
BignumInt mask = (BignumInt)1 << (bitnum % BIGNUM_INT_BITS);
if (value)
/* Read x bit and then reset it */
negative = bignum_bit(point->y, point->curve->fieldBits - 1);
bignum_set_bit(point->y, point->curve->fieldBits - 1, 0);
+ bn_restore_invariant(point->y);
/* Get the x from the y */
point->x = ecp_edx(point->curve, point->y);
/* Curve name is duplicated for Weierstrass form */
if (curve->type == EC_WEIERSTRASS) {
getstring(&data, &len, &p, &slen);
+ if (!p) return NULL;
if (!match_ssh_id(slen, p, curve->name)) return NULL;
}
ec->publicKey.x = NULL;
ec->publicKey.y = NULL;
ec->publicKey.z = NULL;
+ ec->privateKey = NULL;
if (!getmppoint(&data, &len, &ec->publicKey)) {
ecdsa_freekey(ec);
return NULL;
}
- ec->privateKey = NULL;
if (!ec->publicKey.x || !ec->publicKey.y ||
bignum_cmp(ec->publicKey.x, curve->p) >= 0 ||
}
getstring(&sig, &siglen, &p, &slen);
+ if (!p) return 0;
if (ec->publicKey.curve->type == EC_EDWARDS) {
struct ec_point *r;
Bignum s, h;
int stir_pending;
};
-static struct RandPool pool;
int random_active = 0;
+
+#ifdef FUZZING
+/*
+ * Special dummy version of the RNG for use when fuzzing.
+ */
+void random_add_noise(void *noise, int length) { }
+void random_add_heavynoise(void *noise, int length) { }
+void random_ref(void) { }
+void random_unref(void) { }
+int random_byte(void)
+{
+ return 0x45; /* Chosen by eight fair coin tosses */
+}
+void random_get_savedata(void **data, int *len) { }
+#else /* !FUZZING */
+static struct RandPool pool;
long next_noise_collection;
#ifdef RANDOM_DIAGNOSTICS
*data = buf;
random_stir();
}
+#endif
}
term_print_flush(term);
- if (term->logflush)
+ if (term->logflush && term->logctx)
logflush(term->logctx);
}
#define MAX_STDIN_BACKLOG 4096
-void *logctx;
+static void *logctx;
static struct termios orig_termios;
}
} else if (!strcmp(p, "-shareexists")) {
just_test_share_exists = TRUE;
+ } else if (!strcmp(p, "-fuzznet")) {
+ conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ);
+ conf_set_str(conf, CONF_proxy_telnet_command,
+ "%host");
} else {
fprintf(stderr, "plink: unknown option \"%s\"\n", p);
errors = 1;
/* nodelay is only useful if stdin is a terminal device */
int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && isatty(0);
+ /* This is a good place for a fuzzer to fork us. */
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ __AFL_INIT();
+#endif
+
error = back->init(NULL, &backhandle, conf,
conf_get_str(conf, CONF_host),
conf_get_int(conf, CONF_port),
};
Local_Proxy_Socket ret;
- int to_cmd_pipe[2], from_cmd_pipe[2], pid;
+ int to_cmd_pipe[2], from_cmd_pipe[2], pid, proxytype;
- if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD)
+ proxytype = conf_get_int(conf, CONF_proxy_type);
+ if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
return NULL;
- cmd = format_telnet_command(addr, port, conf);
-
ret = snew(struct Socket_localproxy_tag);
ret->fn = &socket_fn_table;
ret->plug = plug;
bufchain_init(&ret->pending_input_data);
bufchain_init(&ret->pending_output_data);
- /*
- * Create the pipes to the proxy command, and spawn the proxy
- * command process.
- */
- if (pipe(to_cmd_pipe) < 0 ||
- pipe(from_cmd_pipe) < 0) {
- ret->error = dupprintf("pipe: %s", strerror(errno));
- sfree(cmd);
- return (Socket)ret;
- }
- cloexec(to_cmd_pipe[1]);
- cloexec(from_cmd_pipe[0]);
-
- pid = fork();
-
- if (pid < 0) {
- ret->error = dupprintf("fork: %s", strerror(errno));
- sfree(cmd);
- return (Socket)ret;
- } else if (pid == 0) {
- close(0);
- close(1);
- dup2(to_cmd_pipe[0], 0);
- dup2(from_cmd_pipe[1], 1);
- close(to_cmd_pipe[0]);
- close(from_cmd_pipe[1]);
- noncloexec(0);
- noncloexec(1);
- execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
- _exit(255);
- }
+ if (proxytype == PROXY_CMD) {
+ cmd = format_telnet_command(addr, port, conf);
+
+ /*
+ * Create the pipes to the proxy command, and spawn the proxy
+ * command process.
+ */
+ if (pipe(to_cmd_pipe) < 0 ||
+ pipe(from_cmd_pipe) < 0) {
+ ret->error = dupprintf("pipe: %s", strerror(errno));
+ sfree(cmd);
+ return (Socket)ret;
+ }
+ cloexec(to_cmd_pipe[1]);
+ cloexec(from_cmd_pipe[0]);
+
+ pid = fork();
+
+ if (pid < 0) {
+ ret->error = dupprintf("fork: %s", strerror(errno));
+ sfree(cmd);
+ return (Socket)ret;
+ } else if (pid == 0) {
+ close(0);
+ close(1);
+ dup2(to_cmd_pipe[0], 0);
+ dup2(from_cmd_pipe[1], 1);
+ close(to_cmd_pipe[0]);
+ close(from_cmd_pipe[1]);
+ noncloexec(0);
+ noncloexec(1);
+ execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
+ _exit(255);
+ }
- sfree(cmd);
+ sfree(cmd);
- close(to_cmd_pipe[0]);
- close(from_cmd_pipe[1]);
+ close(to_cmd_pipe[0]);
+ close(from_cmd_pipe[1]);
- ret->to_cmd = to_cmd_pipe[1];
- ret->from_cmd = from_cmd_pipe[0];
+ ret->to_cmd = to_cmd_pipe[1];
+ ret->from_cmd = from_cmd_pipe[0];
+ } else {
+ cmd = format_telnet_command(addr, port, conf);
+ ret->to_cmd = open("/dev/null", O_WRONLY);
+ if (ret->to_cmd == -1) {
+ ret->error = dupprintf("/dev/null: %s", strerror(errno));
+ sfree(cmd);
+ return (Socket)ret;
+ }
+ ret->from_cmd = open(cmd, O_RDONLY);
+ if (ret->from_cmd == -1) {
+ ret->error = dupprintf("%s: %s", cmd, strerror(errno));
+ sfree(cmd);
+ return (Socket)ret;
+ }
+ sfree(cmd);
+ }
if (!localproxy_by_fromfd)
localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);