From 489c43f3827a9796972f4052ce3248d0c9191f07 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 9 Apr 2019 13:25:50 +0200 Subject: [PATCH] Return STREAM objects from data generating functions Use a consistent style of returning a new STREAM object from functions that output data, rather than requiring an existing structure to be passed in. This generally makes the memory management more straight forward and allows us to do more proper bounds checking of everything. This also adds some new STREAM macros to make it easier to manage them without poking around in the internal structure. --- cssp.c | 179 +++++++++++++++++++++++++++++++------------------ proto.h | 2 +- rdpsnd.c | 6 +- rdpsnd.h | 2 +- rdpsnd_alsa.c | 2 +- rdpsnd_dsp.c | 52 +++++++------- rdpsnd_libao.c | 2 +- rdpsnd_oss.c | 2 +- rdpsnd_pulse.c | 2 +- rdpsnd_sgi.c | 2 +- stream.c | 15 +++++ stream.h | 6 ++ tcp.c | 18 +++-- 13 files changed, 179 insertions(+), 111 deletions(-) diff --git a/cssp.c b/cssp.c index 373ed25..c599791 100644 --- a/cssp.c +++ b/cssp.c @@ -139,13 +139,14 @@ cssp_gss_get_service_name(char *server, gss_name_t * name) } -static RD_BOOL -cssp_gss_wrap(gss_ctx_id_t ctx, STREAM in, STREAM out) +static STREAM +cssp_gss_wrap(gss_ctx_id_t ctx, STREAM in) { int conf_state; OM_uint32 major_status; OM_uint32 minor_status; gss_buffer_desc inbuf, outbuf; + STREAM out; inbuf.value = in->data; inbuf.length = s_length(in); @@ -157,35 +158,36 @@ cssp_gss_wrap(gss_ctx_id_t ctx, STREAM in, STREAM out) { cssp_gss_report_error(GSS_C_GSS_CODE, "Failed to encrypt and sign message", major_status, minor_status); - return False; + return NULL; } if (!conf_state) { logger(Core, Error, "cssp_gss_wrap(), GSS Confidentiality failed, no encryption of message performed."); - return False; + return NULL; } // write enc data to out stream - out->data = out->p = xmalloc(outbuf.length); - out->size = outbuf.length; + out = s_alloc(outbuf.length); out_uint8a(out, outbuf.value, outbuf.length); s_mark_end(out); + s_seek(out, 0); gss_release_buffer(&minor_status, &outbuf); - return True; + return out; } -static RD_BOOL -cssp_gss_unwrap(gss_ctx_id_t ctx, STREAM in, STREAM out) +static STREAM +cssp_gss_unwrap(gss_ctx_id_t ctx, STREAM in) { OM_uint32 major_status; OM_uint32 minor_status; gss_qop_t qop_state; gss_buffer_desc inbuf, outbuf; int conf_state; + STREAM out; inbuf.value = in->data; inbuf.length = s_length(in); @@ -196,17 +198,17 @@ cssp_gss_unwrap(gss_ctx_id_t ctx, STREAM in, STREAM out) { cssp_gss_report_error(GSS_C_GSS_CODE, "Failed to decrypt message", major_status, minor_status); - return False; + return NULL; } - out->data = out->p = xmalloc(outbuf.length); - out->size = outbuf.length; + out = s_alloc(outbuf.length); out_uint8a(out, outbuf.value, outbuf.length); s_mark_end(out); + s_seek(out, 0); gss_release_buffer(&minor_status, &outbuf); - return True; + return out; } @@ -585,10 +587,10 @@ cssp_send_tsrequest(STREAM token, STREAM auth, STREAM pubkey) } -RD_BOOL -cssp_read_tsrequest(STREAM token, STREAM pubkey) +STREAM +cssp_read_tsrequest(RD_BOOL pubkey) { - STREAM s; + STREAM s, out; int length; int tagval; struct stream packet; @@ -596,7 +598,7 @@ cssp_read_tsrequest(STREAM token, STREAM pubkey) s = tcp_recv(NULL, 4); if (s == NULL) - return False; + return NULL; // verify ASN.1 header if (s->p[0] != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) @@ -604,7 +606,7 @@ cssp_read_tsrequest(STREAM token, STREAM pubkey) logger(Protocol, Error, "cssp_read_tsrequest(), expected BER_TAG_SEQUENCE|BER_TAG_CONSTRUCTED, got %x", s->p[0]); - return False; + return NULL; } // peek at first 4 bytes to get full message length @@ -615,7 +617,7 @@ cssp_read_tsrequest(STREAM token, STREAM pubkey) else if (s->p[1] == 0x82) length = (s->p[2] << 8) | s->p[3]; else - return False; + return NULL; // receive the remainings of message s = tcp_recv(s, length); @@ -624,12 +626,12 @@ cssp_read_tsrequest(STREAM token, STREAM pubkey) // parse the response and into nego token if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) - return False; + return NULL; // version [0] if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0)) - return False; + return NULL; if (!s_check_rem(s, length)) { @@ -639,23 +641,23 @@ cssp_read_tsrequest(STREAM token, STREAM pubkey) in_uint8s(s, length); // negoToken [1] - if (token) + if (!pubkey) { if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 1)) - return False; + return NULL; if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) - return False; + return NULL; if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) - return False; + return NULL; if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0)) - return False; + return NULL; if (!ber_in_header(s, &tagval, &length) || tagval != BER_TAG_OCTET_STRING) - return False; + return NULL; if (!s_check_rem(s, length)) { @@ -663,29 +665,29 @@ cssp_read_tsrequest(STREAM token, STREAM pubkey) &packet); } - s_realloc(token, length); - s_reset(token); - out_uint8a(token, s->p, length); - s_mark_end(token); + out = s_alloc(length); + out_uint8a(out, s->p, length); + s_mark_end(out); + s_seek(out, 0); } - // pubKey [3] - if (pubkey) + else { if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 3)) - return False; + return NULL; if (!ber_in_header(s, &tagval, &length) || tagval != BER_TAG_OCTET_STRING) - return False; + return NULL; - pubkey->data = pubkey->p = s->p; - pubkey->end = pubkey->data + length; - pubkey->size = length; + out = s_alloc(length); + out_uint8a(out, s->p, length); + s_mark_end(out); + s_seek(out, 0); } - return True; + return out; } RD_BOOL @@ -702,9 +704,14 @@ cssp_connect(char *server, char *user, char *domain, char *password, STREAM s) gss_OID desired_mech = &_gss_spnego_krb5_mechanism_oid_desc; STREAM ts_creds; - struct stream token = { 0 }; - struct stream pubkey = { 0 }; - struct stream pubkey_cmp = { 0 }; + STREAM token; + STREAM pubkey, pubkey_cmp; + unsigned char *pubkey_data; + unsigned char *pubkey_cmp_data; + unsigned char first_byte; + + RD_BOOL ret; + STREAM blob; // Verify that system gss support spnego if (!cssp_gss_mech_available(desired_mech)) @@ -728,16 +735,19 @@ cssp_connect(char *server, char *user, char *domain, char *password, STREAM s) return False; } - tcp_tls_get_server_pubkey(&pubkey); + pubkey = tcp_tls_get_server_pubkey(); + if (pubkey == NULL) + return False; + pubkey_cmp = NULL; // Enter the spnego loop OM_uint32 actual_services; gss_OID actual_mech; - struct stream blob = { 0 }; gss_ctx = GSS_C_NO_CONTEXT; cred = GSS_C_NO_CREDENTIAL; + token = NULL; input_tok.length = 0; output_tok.length = 0; minor_status = 0; @@ -758,6 +768,11 @@ cssp_connect(char *server, char *user, char *domain, char *password, STREAM s) &actual_mech, &output_tok, &actual_services, &actual_time); + // input_tok might have pointed to token's data, + // but it's safe to free it now after the call + s_free(token); + token = NULL; + if (GSS_ERROR(major_status)) { if (i == 0) @@ -782,39 +797,44 @@ cssp_connect(char *server, char *user, char *domain, char *password, STREAM s) // Send token to server if (output_tok.length != 0) { - if (output_tok.length > token.size) - s_realloc(&token, output_tok.length); - s_reset(&token); + token = s_alloc(output_tok.length); + out_uint8a(token, output_tok.value, output_tok.length); + s_mark_end(token); - out_uint8a(&token, output_tok.value, output_tok.length); - s_mark_end(&token); - - if (!cssp_send_tsrequest(&token, NULL, NULL)) - goto bail_out; + ret = cssp_send_tsrequest(token, NULL, NULL); + s_free(token); + token = NULL; (void) gss_release_buffer(&minor_status, &output_tok); + + if (!ret) + goto bail_out; } // Read token from server if (major_status & GSS_S_CONTINUE_NEEDED) { - (void) gss_release_buffer(&minor_status, &input_tok); - - if (!cssp_read_tsrequest(&token, NULL)) + token = cssp_read_tsrequest(False); + if (token == NULL) goto bail_out; - input_tok.value = token.data; - input_tok.length = s_length(&token); + input_tok.length = s_length(token); + in_uint8p(token, input_tok.value, input_tok.length); } else { // Send encrypted pubkey for verification to server context_established = 1; - if (!cssp_gss_wrap(gss_ctx, &pubkey, &blob)) + blob = cssp_gss_wrap(gss_ctx, pubkey); + if (blob == NULL) goto bail_out; - if (!cssp_send_tsrequest(NULL, NULL, &blob)) + ret = cssp_send_tsrequest(NULL, NULL, blob); + + s_free(blob); + + if (!ret) goto bail_out; context_established = 1; @@ -825,37 +845,62 @@ cssp_connect(char *server, char *user, char *domain, char *password, STREAM s) } while (!context_established); + s_free(token); + // read tsrequest response and decrypt for public key validation - if (!cssp_read_tsrequest(NULL, &blob)) + blob = cssp_read_tsrequest(True); + if (blob == NULL) goto bail_out; - if (!cssp_gss_unwrap(gss_ctx, &blob, &pubkey_cmp)) + pubkey_cmp = cssp_gss_unwrap(gss_ctx, blob); + s_free(blob); + if (pubkey_cmp == NULL) goto bail_out; - pubkey_cmp.data[0] -= 1; + // the first byte gets 1 added before being sent by the server + // in order to protect against replays of the data sent earlier + // by the client + in_uint8(pubkey_cmp, first_byte); + s_seek(pubkey_cmp, 0); + out_uint8(pubkey_cmp, first_byte - 1); + s_seek(pubkey_cmp, 0); // validate public key - if (memcmp(pubkey.data, pubkey_cmp.data, s_length(&pubkey)) != 0) + in_uint8p(pubkey, pubkey_data, s_length(pubkey)); + in_uint8p(pubkey_cmp, pubkey_cmp_data, s_length(pubkey_cmp)); + if ((s_length(pubkey) != s_length(pubkey_cmp)) || + (memcmp(pubkey_data, pubkey_cmp_data, s_length(pubkey)) != 0)) { logger(Core, Error, "cssp_connect(), public key mismatch, cannot guarantee integrity of server connection"); goto bail_out; } + s_free(pubkey); + s_free(pubkey_cmp); + // Send TSCredentials ts_creds = cssp_encode_tscredentials(user, password, domain); - if (!cssp_gss_wrap(gss_ctx, ts_creds, &blob)) - goto bail_out; + blob = cssp_gss_wrap(gss_ctx, ts_creds); s_free(ts_creds); - if (!cssp_send_tsrequest(NULL, &blob, NULL)) + if (blob == NULL) + goto bail_out; + + ret = cssp_send_tsrequest(NULL, blob, NULL); + + s_free(blob); + + if (!ret) goto bail_out; return True; bail_out: - xfree(token.data); + s_free(token); + s_free(pubkey); + s_free(pubkey_cmp); return False; } diff --git a/proto.h b/proto.h index f7f0c6c..2ddb313 100644 --- a/proto.h +++ b/proto.h @@ -227,7 +227,7 @@ char *tcp_get_address(void); RD_BOOL tcp_is_connected(void); void tcp_reset_state(void); RD_BOOL tcp_tls_connect(void); -RD_BOOL tcp_tls_get_server_pubkey(STREAM s); +STREAM tcp_tls_get_server_pubkey(); void tcp_run_ui(RD_BOOL run); /* asn.c */ diff --git a/rdpsnd.c b/rdpsnd.c index b5b8ded..8bb2dbb 100644 --- a/rdpsnd.c +++ b/rdpsnd.c @@ -641,7 +641,7 @@ rdpsnd_queue_write(STREAM s, uint16 tick, uint8 index) queue_hi = next_hi; - packet->s = *s; + packet->s = s; packet->tick = tick; packet->index = index; @@ -675,7 +675,7 @@ rdpsnd_queue_clear(void) while (queue_pending != queue_hi) { packet = &packet_queue[queue_pending]; - xfree(packet->s.data); + s_free(packet->s); queue_pending = (queue_pending + 1) % MAX_QUEUE; } @@ -740,7 +740,7 @@ rdpsnd_queue_complete_pending(void) (packet->completion_tv.tv_usec - packet->arrive_tv.tv_usec); elapsed /= 1000; - xfree(packet->s.data); + s_free(packet->s); rdpsnd_send_waveconfirm((packet->tick + elapsed) % 65536, packet->index); queue_pending = (queue_pending + 1) % MAX_QUEUE; } diff --git a/rdpsnd.h b/rdpsnd.h index 81e87c4..b31e175 100644 --- a/rdpsnd.h +++ b/rdpsnd.h @@ -19,7 +19,7 @@ struct audio_packet { - struct stream s; + STREAM s; uint16 tick; uint8 index; diff --git a/rdpsnd_alsa.c b/rdpsnd_alsa.c index 26e5cc8..25fbc31 100644 --- a/rdpsnd_alsa.c +++ b/rdpsnd_alsa.c @@ -376,7 +376,7 @@ alsa_play(void) return; packet = rdpsnd_queue_current_packet(); - out = &packet->s; + out = packet->s; next_tick = rdpsnd_queue_next_tick(); diff --git a/rdpsnd_dsp.c b/rdpsnd_dsp.c index 9661e09..8cc10fb 100644 --- a/rdpsnd_dsp.c +++ b/rdpsnd_dsp.c @@ -175,8 +175,8 @@ rdpsnd_dsp_resample_supported(RD_WAVEFORMATEX * format) return True; } -uint32 -rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, +STREAM +rdpsnd_dsp_resample(unsigned char *in, unsigned int size, RD_WAVEFORMATEX * format, RD_BOOL stream_be) { UNUSED(stream_be); @@ -190,13 +190,15 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, int innum, outnum; unsigned char *tmpdata = NULL, *tmp = NULL; int samplewidth = format->wBitsPerSample / 8; + STREAM out; int outsize = 0; + unsigned char *data; int i; if ((resample_to_bitspersample == format->wBitsPerSample) && (resample_to_channels == format->nChannels) && (resample_to_srate == format->nSamplesPerSec)) - return 0; + return NULL; #ifdef B_ENDIAN if (!stream_be) @@ -260,7 +262,7 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, { logger(Sound, Warning, "rdpsndp_dsp_resample_set(), no sample rate converter available"); - return 0; + return NULL; } outnum = ((float) innum * ((float) resample_to_srate / (float) format->nSamplesPerSec)) + 1; @@ -285,8 +287,9 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, xfree(infloat); outsize = resample_data.output_frames_gen * resample_to_channels * samplewidth; - *out = (unsigned char *) xmalloc(outsize); - src_float_to_short_array(outfloat, (short *) *out, + out = s_alloc(outsize); + out_uint8p(out, data, outsize); + src_float_to_short_array(outfloat, (short *) data, resample_data.output_frames_gen * resample_to_channels); xfree(outfloat); @@ -302,8 +305,9 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, outnum = (innum * ratio1k) / 1000; outsize = outnum * samplewidth; - *out = (unsigned char *) xmalloc(outsize); - bzero(*out, outsize); + out = s_alloc(outsize); + out_uint8p(out, data, outsize); + bzero(data, outsize); for (i = 0; i < outsize / (resample_to_channels * samplewidth); i++) { @@ -331,7 +335,7 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, cval1 += (sint8) (cval2 * part) / 100; - memcpy(*out + (i * resample_to_channels * samplewidth) + + memcpy(data + (i * resample_to_channels * samplewidth) + (samplewidth * j), &cval1, samplewidth); } } @@ -349,14 +353,14 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, sval1 += (sint16) (sval2 * part) / 100; - memcpy(*out + (i * resample_to_channels * samplewidth) + + memcpy(data + (i * resample_to_channels * samplewidth) + (samplewidth * j), &sval1, samplewidth); } } #else /* Nearest neighbor search */ for (j = 0; j < resample_to_channels; j++) { - memcpy(*out + (i * resample_to_channels * samplewidth) + (samplewidth * j), + memcpy(out + (i * resample_to_channels * samplewidth) + (samplewidth * j), in + (source * resample_to_channels * samplewidth) + (samplewidth * j), samplewidth); } @@ -378,7 +382,7 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, { for (i = 0; i < outsize; i++) { - *out[i] = *out[i * 2]; + data[i] = data[i * 2]; } outsize /= 2; } @@ -386,16 +390,17 @@ rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, #ifdef B_ENDIAN if (!stream_be) - rdpsnd_dsp_swapbytes(*out, outsize, format); + rdpsnd_dsp_swapbytes(data, outsize, format); #endif - return outsize; + + return out; } STREAM rdpsnd_dsp_process(unsigned char *data, unsigned int size, struct audio_driver * current_driver, RD_WAVEFORMATEX * format) { - static struct stream out; + STREAM out; RD_BOOL stream_be = False; /* softvol and byteswap do not change the amount of data they @@ -411,20 +416,19 @@ rdpsnd_dsp_process(unsigned char *data, unsigned int size, struct audio_driver * } #endif - out.data = NULL; + out = NULL; if (current_driver->need_resampling) - out.size = rdpsnd_dsp_resample(&out.data, data, size, format, stream_be); + out = rdpsnd_dsp_resample(data, size, format, stream_be); - if (out.data == NULL) + if (out == NULL) { - out.data = (unsigned char *) xmalloc(size); - memcpy(out.data, data, size); - out.size = size; + out = s_alloc(size); + out_uint8a(out, data, size); } - out.p = out.data; - out.end = out.p + out.size; + s_mark_end(out); + s_seek(out, 0); - return &out; + return out; } diff --git a/rdpsnd_libao.c b/rdpsnd_libao.c index 6d1996f..3a59788 100644 --- a/rdpsnd_libao.c +++ b/rdpsnd_libao.c @@ -167,7 +167,7 @@ libao_play(void) return; packet = rdpsnd_queue_current_packet(); - out = &packet->s; + out = packet->s; next_tick = rdpsnd_queue_next_tick(); diff --git a/rdpsnd_oss.c b/rdpsnd_oss.c index ae5df21..0705236 100644 --- a/rdpsnd_oss.c +++ b/rdpsnd_oss.c @@ -410,7 +410,7 @@ oss_play(void) return; packet = rdpsnd_queue_current_packet(); - out = &packet->s; + out = packet->s; len = s_remaining(out); diff --git a/rdpsnd_pulse.c b/rdpsnd_pulse.c index 9bcee39..a763541 100644 --- a/rdpsnd_pulse.c +++ b/rdpsnd_pulse.c @@ -1154,7 +1154,7 @@ pulse_play(void) do { packet = rdpsnd_queue_current_packet(); - out = &packet->s; + out = packet->s; ti = pa_stream_get_timing_info(playback_stream); if (ti == NULL) diff --git a/rdpsnd_sgi.c b/rdpsnd_sgi.c index d94f648..d62f12c 100644 --- a/rdpsnd_sgi.c +++ b/rdpsnd_sgi.c @@ -254,7 +254,7 @@ sgi_play(void) return; packet = rdpsnd_queue_current_packet(); - out = (STREAM) (void *) &(packet->s); + out = packet->s; len = s_remaining(out); diff --git a/stream.c b/stream.c index 8400446..a4b9542 100644 --- a/stream.c +++ b/stream.c @@ -37,6 +37,19 @@ s_alloc(unsigned int size) return s; } +STREAM +s_inherit(unsigned char *data, unsigned int size) +{ + STREAM s; + + s = xmalloc(sizeof(struct stream)); + memset(s, 0, sizeof(struct stream)); + s->p = s->data = data; + s->size = size; + + return s; +} + void s_realloc(STREAM s, unsigned int size) { @@ -71,6 +84,8 @@ s_reset(STREAM s) void s_free(STREAM s) { + if (s == NULL) + return; free(s->data); free(s); } diff --git a/stream.h b/stream.h index b9e16dc..5e7464b 100644 --- a/stream.h +++ b/stream.h @@ -43,6 +43,8 @@ typedef struct stream /* Return a newly allocated STREAM object of the specified size */ STREAM s_alloc(unsigned int size); +/* Wrap an existing buffer in a STREAM object, transferring ownership */ +STREAM s_inherit(unsigned char *data, unsigned int size); /* Resize an existing STREAM object, keeping all data and offsets intact */ void s_realloc(STREAM s, unsigned int size); /* Free STREAM object and its associated buffer */ @@ -60,6 +62,10 @@ size_t in_ansi_string(STREAM s, char *string, size_t len); #define s_push_layer(s,h,n) { (s)->h = (s)->p; (s)->p += n; } #define s_pop_layer(s,h) (s)->p = (s)->h; #define s_mark_end(s) (s)->end = (s)->p; +/* Return current read offset in the STREAM */ +#define s_tell(s) (size_t)((s)->p - (s)->data) +/* Set current read offset in the STREAM */ +#define s_seek(s,o) (s)->p = (s)->data; s_assert_r(s,o); (s)->p += o; /* Returns number of bytes that can still be read from STREAM */ #define s_remaining(s) (size_t)((s)->end - (s)->p) #define s_check_rem(s,n) (((s)->p <= (s)->end) && ((size_t)n <= s_remaining(s))) diff --git a/tcp.c b/tcp.c index 726f6c0..8634631 100644 --- a/tcp.c +++ b/tcp.c @@ -399,8 +399,8 @@ fail: } /* Get public key from server of TLS 1.x connection */ -RD_BOOL -tcp_tls_get_server_pubkey(STREAM s) +STREAM +tcp_tls_get_server_pubkey() { int ret; unsigned int list_size; @@ -413,8 +413,7 @@ tcp_tls_get_server_pubkey(STREAM s) int pk_size; uint8_t pk_data[1024]; - s->data = s->p = NULL; - s->size = 0; + STREAM s = NULL; cert_list = gnutls_certificate_get_peers(g_tls_session, &list_size); @@ -466,11 +465,10 @@ tcp_tls_get_server_pubkey(STREAM s) goto out; } - s->size = pk_size; - s->data = s->p = xmalloc(s->size); - memcpy((void *)s->data, (void *)pk_data, pk_size); - s->p = s->data; - s->end = s->p + s->size; + s = s_alloc(pk_size); + out_uint8a(s, pk_data, pk_size); + s_mark_end(s); + s_seek(s, 0); out: if ((e.size != 0) && (e.data)) { @@ -481,7 +479,7 @@ out: free(m.data); } - return (s->size != 0); + return s; } /* Helper function to determine if rdesktop should resolve hostnames again or not */