Line data Source code
1 : /* Copyright (c) 2001 Matej Pfajfar.
2 : * Copyright (c) 2001-2004, Roger Dingledine.
3 : * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 : * Copyright (c) 2007-2021, The Tor Project, Inc. */
5 : /* See LICENSE for licensing information */
6 :
7 : /**
8 : * @file relay_handshake.c
9 : * @brief Functions to implement the relay-only parts of our
10 : * connection handshake.
11 : *
12 : * Some parts of our TLS link handshake are only done by relays (including
13 : * bridges). Specifically, only relays need to send CERTS cells; only
14 : * relays need to send or receive AUTHCHALLENGE cells, and only relays need to
15 : * send or receive AUTHENTICATE cells.
16 : **/
17 :
18 : #include "orconfig.h"
19 : #include "core/or/or.h"
20 : #include "feature/relay/relay_handshake.h"
21 :
22 : #include "app/config/config.h"
23 : #include "core/or/connection_or.h"
24 : #include "lib/crypt_ops/crypto_rand.h"
25 : #include "trunnel/link_handshake.h"
26 : #include "feature/relay/routerkeys.h"
27 : #include "feature/nodelist/torcert.h"
28 :
29 : #include "core/or/or_connection_st.h"
30 : #include "core/or/or_handshake_certs_st.h"
31 : #include "core/or/or_handshake_state_st.h"
32 : #include "core/or/var_cell_st.h"
33 :
34 : #include "lib/tls/tortls.h"
35 : #include "lib/tls/x509.h"
36 :
37 : /** Helper used to add an encoded certs to a cert cell */
38 : static void
39 14 : add_certs_cell_cert_helper(certs_cell_t *certs_cell,
40 : uint8_t cert_type,
41 : const uint8_t *cert_encoded,
42 : size_t cert_len)
43 : {
44 14 : tor_assert(cert_len <= UINT16_MAX);
45 14 : certs_cell_cert_t *ccc = certs_cell_cert_new();
46 14 : ccc->cert_type = cert_type;
47 14 : ccc->cert_len = cert_len;
48 14 : certs_cell_cert_setlen_body(ccc, cert_len);
49 14 : memcpy(certs_cell_cert_getarray_body(ccc), cert_encoded, cert_len);
50 :
51 14 : certs_cell_add_certs(certs_cell, ccc);
52 14 : }
53 :
54 : /** Add an encoded X509 cert (stored as <b>cert_len</b> bytes at
55 : * <b>cert_encoded</b>) to the trunnel certs_cell_t object that we are
56 : * building in <b>certs_cell</b>. Set its type field to <b>cert_type</b>.
57 : * (If <b>cert</b> is NULL, take no action.) */
58 : static void
59 8 : add_x509_cert(certs_cell_t *certs_cell,
60 : uint8_t cert_type,
61 : const tor_x509_cert_t *cert)
62 : {
63 8 : if (NULL == cert)
64 0 : return;
65 :
66 8 : const uint8_t *cert_encoded = NULL;
67 8 : size_t cert_len;
68 8 : tor_x509_cert_get_der(cert, &cert_encoded, &cert_len);
69 :
70 8 : add_certs_cell_cert_helper(certs_cell, cert_type, cert_encoded, cert_len);
71 : }
72 :
73 : /** Add an Ed25519 cert from <b>cert</b> to the trunnel certs_cell_t object
74 : * that we are building in <b>certs_cell</b>. Set its type field to
75 : * <b>cert_type</b>. (If <b>cert</b> is NULL, take no action.) */
76 : static void
77 8 : add_ed25519_cert(certs_cell_t *certs_cell,
78 : uint8_t cert_type,
79 : const tor_cert_t *cert)
80 : {
81 8 : if (NULL == cert)
82 : return;
83 :
84 4 : add_certs_cell_cert_helper(certs_cell, cert_type,
85 4 : cert->encoded, cert->encoded_len);
86 : }
87 :
88 : #ifdef TOR_UNIT_TESTS
89 : int certs_cell_ed25519_disabled_for_testing = 0;
90 : #else
91 : #define certs_cell_ed25519_disabled_for_testing 0
92 : #endif
93 :
94 : /** Send a CERTS cell on the connection <b>conn</b>. Return 0 on success, -1
95 : * on failure. */
96 : int
97 4 : connection_or_send_certs_cell(or_connection_t *conn)
98 : {
99 4 : const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL;
100 4 : tor_x509_cert_t *own_link_cert = NULL;
101 4 : var_cell_t *cell;
102 :
103 4 : certs_cell_t *certs_cell = NULL;
104 :
105 4 : tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
106 :
107 4 : if (! conn->handshake_state)
108 : return -1;
109 :
110 4 : const int conn_in_server_mode = ! conn->handshake_state->started_here;
111 :
112 : /* Get the encoded values of the X509 certificates */
113 4 : if (tor_tls_get_my_certs(conn_in_server_mode,
114 : &global_link_cert, &id_cert) < 0)
115 : return -1;
116 :
117 4 : if (conn_in_server_mode) {
118 2 : own_link_cert = tor_tls_get_own_cert(conn->tls);
119 : }
120 4 : tor_assert(id_cert);
121 :
122 4 : certs_cell = certs_cell_new();
123 :
124 : /* Start adding certs. First the link cert or auth1024 cert. */
125 4 : if (conn_in_server_mode) {
126 2 : tor_assert_nonfatal(own_link_cert);
127 2 : add_x509_cert(certs_cell,
128 : OR_CERT_TYPE_TLS_LINK, own_link_cert);
129 : } else {
130 2 : tor_assert(global_link_cert);
131 2 : add_x509_cert(certs_cell,
132 : OR_CERT_TYPE_AUTH_1024, global_link_cert);
133 : }
134 :
135 : /* Next the RSA->RSA ID cert */
136 4 : add_x509_cert(certs_cell,
137 : OR_CERT_TYPE_ID_1024, id_cert);
138 :
139 : /* Next the Ed25519 certs */
140 4 : add_ed25519_cert(certs_cell,
141 : CERTTYPE_ED_ID_SIGN,
142 4 : get_master_signing_key_cert());
143 4 : if (conn_in_server_mode) {
144 2 : tor_assert_nonfatal(conn->handshake_state->own_link_cert ||
145 : certs_cell_ed25519_disabled_for_testing);
146 2 : add_ed25519_cert(certs_cell,
147 : CERTTYPE_ED_SIGN_LINK,
148 2 : conn->handshake_state->own_link_cert);
149 : } else {
150 2 : add_ed25519_cert(certs_cell,
151 : CERTTYPE_ED_SIGN_AUTH,
152 2 : get_current_auth_key_cert());
153 : }
154 :
155 : /* And finally the crosscert. */
156 : {
157 4 : const uint8_t *crosscert=NULL;
158 4 : size_t crosscert_len;
159 4 : get_master_rsa_crosscert(&crosscert, &crosscert_len);
160 4 : if (crosscert) {
161 2 : add_certs_cell_cert_helper(certs_cell,
162 : CERTTYPE_RSA1024_ID_EDID,
163 : crosscert, crosscert_len);
164 : }
165 : }
166 :
167 : /* We've added all the certs; make the cell. */
168 4 : certs_cell->n_certs = certs_cell_getlen_certs(certs_cell);
169 :
170 4 : ssize_t alloc_len = certs_cell_encoded_len(certs_cell);
171 4 : tor_assert(alloc_len >= 0 && alloc_len <= UINT16_MAX);
172 4 : cell = var_cell_new(alloc_len);
173 4 : cell->command = CELL_CERTS;
174 4 : ssize_t enc_len = certs_cell_encode(cell->payload, alloc_len, certs_cell);
175 4 : tor_assert(enc_len > 0 && enc_len <= alloc_len);
176 4 : cell->payload_len = enc_len;
177 :
178 4 : connection_or_write_var_cell_to_buf(cell, conn);
179 4 : var_cell_free(cell);
180 4 : certs_cell_free(certs_cell);
181 4 : tor_x509_cert_free(own_link_cert);
182 :
183 4 : return 0;
184 : }
185 :
186 : #ifdef TOR_UNIT_TESTS
187 : int testing__connection_or_pretend_TLSSECRET_is_supported = 0;
188 : #else
189 : #define testing__connection_or_pretend_TLSSECRET_is_supported 0
190 : #endif
191 :
192 : /** Return true iff <b>challenge_type</b> is an AUTHCHALLENGE type that
193 : * we can send and receive. */
194 : int
195 46 : authchallenge_type_is_supported(uint16_t challenge_type)
196 : {
197 46 : switch (challenge_type) {
198 : case AUTHTYPE_RSA_SHA256_TLSSECRET:
199 : #ifdef HAVE_WORKING_TOR_TLS_GET_TLSSECRETS
200 : return 1;
201 : #else
202 : return testing__connection_or_pretend_TLSSECRET_is_supported;
203 : #endif
204 : case AUTHTYPE_ED25519_SHA256_RFC5705:
205 : return 1;
206 6 : case AUTHTYPE_RSA_SHA256_RFC5705:
207 : default:
208 6 : return 0;
209 : }
210 : }
211 :
212 : /** Return true iff <b>challenge_type_a</b> is one that we would rather
213 : * use than <b>challenge_type_b</b>. */
214 : int
215 1 : authchallenge_type_is_better(uint16_t challenge_type_a,
216 : uint16_t challenge_type_b)
217 : {
218 : /* Any supported type is better than an unsupported one;
219 : * all unsupported types are equally bad. */
220 1 : if (!authchallenge_type_is_supported(challenge_type_a))
221 : return 0;
222 1 : if (!authchallenge_type_is_supported(challenge_type_b))
223 : return 1;
224 : /* It happens that types are superior in numerically ascending order.
225 : * If that ever changes, this must change too. */
226 1 : return (challenge_type_a > challenge_type_b);
227 : }
228 :
229 : /** Send an AUTH_CHALLENGE cell on the connection <b>conn</b>. Return 0
230 : * on success, -1 on failure. */
231 : int
232 2 : connection_or_send_auth_challenge_cell(or_connection_t *conn)
233 : {
234 2 : var_cell_t *cell = NULL;
235 2 : int r = -1;
236 2 : tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
237 :
238 2 : if (! conn->handshake_state)
239 : return -1;
240 :
241 2 : auth_challenge_cell_t *ac = auth_challenge_cell_new();
242 :
243 2 : tor_assert(sizeof(ac->challenge) == 32);
244 2 : crypto_rand((char*)ac->challenge, sizeof(ac->challenge));
245 :
246 2 : if (authchallenge_type_is_supported(AUTHTYPE_RSA_SHA256_TLSSECRET))
247 2 : auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET);
248 : /* Disabled, because everything that supports this method also supports
249 : * the much-superior ED25519_SHA256_RFC5705 */
250 : /* auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_RFC5705); */
251 2 : if (authchallenge_type_is_supported(AUTHTYPE_ED25519_SHA256_RFC5705))
252 2 : auth_challenge_cell_add_methods(ac, AUTHTYPE_ED25519_SHA256_RFC5705);
253 2 : auth_challenge_cell_set_n_methods(ac,
254 2 : auth_challenge_cell_getlen_methods(ac));
255 :
256 2 : cell = var_cell_new(auth_challenge_cell_encoded_len(ac));
257 2 : ssize_t len = auth_challenge_cell_encode(cell->payload, cell->payload_len,
258 : ac);
259 2 : if (len != cell->payload_len) {
260 : /* LCOV_EXCL_START */
261 : log_warn(LD_BUG, "Encoded auth challenge cell length not as expected");
262 : goto done;
263 : /* LCOV_EXCL_STOP */
264 : }
265 2 : cell->command = CELL_AUTH_CHALLENGE;
266 :
267 2 : connection_or_write_var_cell_to_buf(cell, conn);
268 2 : r = 0;
269 :
270 2 : done:
271 2 : var_cell_free(cell);
272 2 : auth_challenge_cell_free(ac);
273 :
274 2 : return r;
275 : }
276 :
277 : /** Compute the main body of an AUTHENTICATE cell that a client can use
278 : * to authenticate itself on a v3 handshake for <b>conn</b>. Return it
279 : * in a var_cell_t.
280 : *
281 : * If <b>server</b> is true, only calculate the first
282 : * V3_AUTH_FIXED_PART_LEN bytes -- the part of the authenticator that's
283 : * determined by the rest of the handshake, and which match the provided value
284 : * exactly.
285 : *
286 : * If <b>server</b> is false and <b>signing_key</b> is NULL, calculate the
287 : * first V3_AUTH_BODY_LEN bytes of the authenticator (that is, everything
288 : * that should be signed), but don't actually sign it.
289 : *
290 : * If <b>server</b> is false and <b>signing_key</b> is provided, calculate the
291 : * entire authenticator, signed with <b>signing_key</b>.
292 : *
293 : * Return the length of the cell body on success, and -1 on failure.
294 : */
295 : var_cell_t *
296 28 : connection_or_compute_authenticate_cell_body(or_connection_t *conn,
297 : const int authtype,
298 : crypto_pk_t *signing_key,
299 : const ed25519_keypair_t *ed_signing_key,
300 : int server)
301 : {
302 28 : auth1_t *auth = NULL;
303 28 : auth_ctx_t *ctx = auth_ctx_new();
304 28 : var_cell_t *result = NULL;
305 28 : int old_tlssecrets_algorithm = 0;
306 28 : const char *authtype_str = NULL;
307 :
308 28 : int is_ed = 0;
309 :
310 : /* assert state is reasonable XXXX */
311 28 : switch (authtype) {
312 : case AUTHTYPE_RSA_SHA256_TLSSECRET:
313 : authtype_str = "AUTH0001";
314 : old_tlssecrets_algorithm = 1;
315 : break;
316 0 : case AUTHTYPE_RSA_SHA256_RFC5705:
317 0 : authtype_str = "AUTH0002";
318 0 : break;
319 8 : case AUTHTYPE_ED25519_SHA256_RFC5705:
320 8 : authtype_str = "AUTH0003";
321 8 : is_ed = 1;
322 8 : break;
323 : default:
324 0 : tor_assert(0);
325 : break;
326 : }
327 :
328 28 : auth = auth1_new();
329 28 : ctx->is_ed = is_ed;
330 :
331 : /* Type: 8 bytes. */
332 28 : memcpy(auth1_getarray_type(auth), authtype_str, 8);
333 :
334 : {
335 28 : const tor_x509_cert_t *id_cert=NULL;
336 28 : const common_digests_t *my_digests, *their_digests;
337 28 : const uint8_t *my_id, *their_id, *client_id, *server_id;
338 28 : if (tor_tls_get_my_certs(server, NULL, &id_cert))
339 0 : goto err;
340 28 : my_digests = tor_x509_cert_get_id_digests(id_cert);
341 28 : their_digests =
342 28 : tor_x509_cert_get_id_digests(conn->handshake_state->certs->id_cert);
343 28 : tor_assert(my_digests);
344 28 : tor_assert(their_digests);
345 28 : my_id = (uint8_t*)my_digests->d[DIGEST_SHA256];
346 28 : their_id = (uint8_t*)their_digests->d[DIGEST_SHA256];
347 :
348 28 : client_id = server ? their_id : my_id;
349 28 : server_id = server ? my_id : their_id;
350 :
351 : /* Client ID digest: 32 octets. */
352 28 : memcpy(auth->cid, client_id, 32);
353 :
354 : /* Server ID digest: 32 octets. */
355 28 : memcpy(auth->sid, server_id, 32);
356 : }
357 :
358 28 : if (is_ed) {
359 8 : const ed25519_public_key_t *my_ed_id, *their_ed_id;
360 8 : if (!conn->handshake_state->certs->ed_id_sign) {
361 1 : log_warn(LD_OR, "Ed authenticate without Ed ID cert from peer.");
362 1 : goto err;
363 : }
364 7 : my_ed_id = get_master_identity_key();
365 7 : their_ed_id = &conn->handshake_state->certs->ed_id_sign->signing_key;
366 :
367 7 : const uint8_t *cid_ed = (server ? their_ed_id : my_ed_id)->pubkey;
368 7 : const uint8_t *sid_ed = (server ? my_ed_id : their_ed_id)->pubkey;
369 :
370 7 : memcpy(auth->u1_cid_ed, cid_ed, ED25519_PUBKEY_LEN);
371 7 : memcpy(auth->u1_sid_ed, sid_ed, ED25519_PUBKEY_LEN);
372 : }
373 :
374 : {
375 27 : crypto_digest_t *server_d, *client_d;
376 27 : if (server) {
377 7 : server_d = conn->handshake_state->digest_sent;
378 7 : client_d = conn->handshake_state->digest_received;
379 : } else {
380 20 : client_d = conn->handshake_state->digest_sent;
381 20 : server_d = conn->handshake_state->digest_received;
382 : }
383 :
384 : /* Server log digest : 32 octets */
385 27 : crypto_digest_get_digest(server_d, (char*)auth->slog, 32);
386 :
387 : /* Client log digest : 32 octets */
388 27 : crypto_digest_get_digest(client_d, (char*)auth->clog, 32);
389 : }
390 :
391 : {
392 : /* Digest of cert used on TLS link : 32 octets. */
393 27 : tor_x509_cert_t *cert = NULL;
394 27 : if (server) {
395 7 : cert = tor_tls_get_own_cert(conn->tls);
396 : } else {
397 20 : cert = tor_tls_get_peer_cert(conn->tls);
398 : }
399 27 : if (!cert) {
400 0 : log_warn(LD_OR, "Unable to find cert when making %s data.",
401 : authtype_str);
402 0 : goto err;
403 : }
404 :
405 54 : memcpy(auth->scert,
406 27 : tor_x509_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32);
407 :
408 27 : tor_x509_cert_free(cert);
409 : }
410 :
411 : /* HMAC of clientrandom and serverrandom using master key : 32 octets */
412 27 : if (old_tlssecrets_algorithm) {
413 20 : if (tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets) < 0) {
414 0 : log_fn(LOG_PROTOCOL_WARN, LD_OR, "Somebody asked us for an older TLS "
415 : "authentication method (AUTHTYPE_RSA_SHA256_TLSSECRET) "
416 : "which we don't support.");
417 : }
418 : } else {
419 7 : char label[128];
420 7 : tor_snprintf(label, sizeof(label),
421 : "EXPORTER FOR TOR TLS CLIENT BINDING %s", authtype_str);
422 7 : int r = tor_tls_export_key_material(conn->tls, auth->tlssecrets,
423 : auth->cid, sizeof(auth->cid),
424 : label);
425 7 : if (r < 0) {
426 0 : if (r != -2)
427 0 : log_warn(LD_BUG, "TLS key export failed for unknown reason.");
428 : // If r == -2, this was openssl bug 7712.
429 0 : goto err;
430 : }
431 : }
432 :
433 : /* 8 octets were reserved for the current time, but we're trying to get out
434 : * of the habit of sending time around willynilly. Fortunately, nothing
435 : * checks it. That's followed by 16 bytes of nonce. */
436 27 : crypto_rand((char*)auth->rand, 24);
437 :
438 27 : ssize_t maxlen = auth1_encoded_len(auth, ctx);
439 27 : if (ed_signing_key && is_ed) {
440 4 : maxlen += ED25519_SIG_LEN;
441 23 : } else if (signing_key && !is_ed) {
442 16 : maxlen += crypto_pk_keysize(signing_key);
443 : }
444 :
445 27 : const int AUTH_CELL_HEADER_LEN = 4; /* 2 bytes of type, 2 bytes of length */
446 27 : result = var_cell_new(AUTH_CELL_HEADER_LEN + maxlen);
447 27 : uint8_t *const out = result->payload + AUTH_CELL_HEADER_LEN;
448 27 : const size_t outlen = maxlen;
449 27 : ssize_t len;
450 :
451 27 : result->command = CELL_AUTHENTICATE;
452 27 : set_uint16(result->payload, htons(authtype));
453 :
454 27 : if ((len = auth1_encode(out, outlen, auth, ctx)) < 0) {
455 : /* LCOV_EXCL_START */
456 : log_warn(LD_BUG, "Unable to encode signed part of AUTH1 data.");
457 : goto err;
458 : /* LCOV_EXCL_STOP */
459 : }
460 :
461 27 : if (server) {
462 7 : auth1_t *tmp = NULL;
463 7 : ssize_t len2 = auth1_parse(&tmp, out, len, ctx);
464 7 : if (!tmp) {
465 : /* LCOV_EXCL_START */
466 : log_warn(LD_BUG, "Unable to parse signed part of AUTH1 data that "
467 : "we just encoded");
468 : goto err;
469 : /* LCOV_EXCL_STOP */
470 : }
471 7 : result->payload_len = (tmp->end_of_signed - result->payload);
472 :
473 7 : auth1_free(tmp);
474 7 : if (len2 != len) {
475 : /* LCOV_EXCL_START */
476 : log_warn(LD_BUG, "Mismatched length when re-parsing AUTH1 data.");
477 : goto err;
478 : /* LCOV_EXCL_STOP */
479 : }
480 7 : goto done;
481 : }
482 :
483 20 : if (ed_signing_key && is_ed) {
484 4 : ed25519_signature_t sig;
485 4 : if (ed25519_sign(&sig, out, len, ed_signing_key) < 0) {
486 : /* LCOV_EXCL_START */
487 : log_warn(LD_BUG, "Unable to sign ed25519 authentication data");
488 : goto err;
489 : /* LCOV_EXCL_STOP */
490 : }
491 4 : auth1_setlen_sig(auth, ED25519_SIG_LEN);
492 4 : memcpy(auth1_getarray_sig(auth), sig.sig, ED25519_SIG_LEN);
493 :
494 16 : } else if (signing_key && !is_ed) {
495 16 : auth1_setlen_sig(auth, crypto_pk_keysize(signing_key));
496 :
497 16 : char d[32];
498 16 : crypto_digest256(d, (char*)out, len, DIGEST_SHA256);
499 32 : int siglen = crypto_pk_private_sign(signing_key,
500 16 : (char*)auth1_getarray_sig(auth),
501 : auth1_getlen_sig(auth),
502 : d, 32);
503 16 : if (siglen < 0) {
504 0 : log_warn(LD_OR, "Unable to sign AUTH1 data.");
505 0 : goto err;
506 : }
507 :
508 16 : auth1_setlen_sig(auth, siglen);
509 : }
510 :
511 20 : len = auth1_encode(out, outlen, auth, ctx);
512 20 : if (len < 0) {
513 : /* LCOV_EXCL_START */
514 : log_warn(LD_BUG, "Unable to encode signed AUTH1 data.");
515 : goto err;
516 : /* LCOV_EXCL_STOP */
517 : }
518 20 : tor_assert(len + AUTH_CELL_HEADER_LEN <= result->payload_len);
519 20 : result->payload_len = len + AUTH_CELL_HEADER_LEN;
520 20 : set_uint16(result->payload+2, htons(len));
521 :
522 20 : goto done;
523 :
524 1 : err:
525 1 : var_cell_free(result);
526 1 : result = NULL;
527 28 : done:
528 28 : auth1_free(auth);
529 28 : auth_ctx_free(ctx);
530 28 : return result;
531 : }
532 :
533 : /** Send an AUTHENTICATE cell on the connection <b>conn</b>. Return 0 on
534 : * success, -1 on failure */
535 20 : MOCK_IMPL(int,
536 : connection_or_send_authenticate_cell,(or_connection_t *conn, int authtype))
537 : {
538 20 : var_cell_t *cell;
539 20 : crypto_pk_t *pk = tor_tls_get_my_client_auth_key();
540 : /* XXXX make sure we're actually supposed to send this! */
541 :
542 20 : if (!pk) {
543 0 : log_warn(LD_BUG, "Can't compute authenticate cell: no client auth key");
544 0 : return -1;
545 : }
546 20 : if (! authchallenge_type_is_supported(authtype)) {
547 0 : log_warn(LD_BUG, "Tried to send authenticate cell with unknown "
548 : "authentication type %d", authtype);
549 0 : return -1;
550 : }
551 :
552 20 : cell = connection_or_compute_authenticate_cell_body(conn,
553 : authtype,
554 : pk,
555 20 : get_current_auth_keypair(),
556 : 0 /* not server */);
557 20 : if (! cell) {
558 0 : log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unable to compute authenticate cell!");
559 0 : return -1;
560 : }
561 20 : connection_or_write_var_cell_to_buf(cell, conn);
562 20 : var_cell_free(cell);
563 :
564 20 : return 0;
565 : }
|