|
1 /* crypto/bio/bio_lib.c */ |
|
2 /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) |
|
3 * All rights reserved. |
|
4 * |
|
5 * This package is an SSL implementation written |
|
6 * by Eric Young (eay@cryptsoft.com). |
|
7 * The implementation was written so as to conform with Netscapes SSL. |
|
8 * |
|
9 * This library is free for commercial and non-commercial use as long as |
|
10 * the following conditions are aheared to. The following conditions |
|
11 * apply to all code found in this distribution, be it the RC4, RSA, |
|
12 * lhash, DES, etc., code; not just the SSL code. The SSL documentation |
|
13 * included with this distribution is covered by the same copyright terms |
|
14 * except that the holder is Tim Hudson (tjh@cryptsoft.com). |
|
15 * |
|
16 * Copyright remains Eric Young's, and as such any Copyright notices in |
|
17 * the code are not to be removed. |
|
18 * If this package is used in a product, Eric Young should be given attribution |
|
19 * as the author of the parts of the library used. |
|
20 * This can be in the form of a textual message at program startup or |
|
21 * in documentation (online or textual) provided with the package. |
|
22 * |
|
23 * Redistribution and use in source and binary forms, with or without |
|
24 * modification, are permitted provided that the following conditions |
|
25 * are met: |
|
26 * 1. Redistributions of source code must retain the copyright |
|
27 * notice, this list of conditions and the following disclaimer. |
|
28 * 2. Redistributions in binary form must reproduce the above copyright |
|
29 * notice, this list of conditions and the following disclaimer in the |
|
30 * documentation and/or other materials provided with the distribution. |
|
31 * 3. All advertising materials mentioning features or use of this software |
|
32 * must display the following acknowledgement: |
|
33 * "This product includes cryptographic software written by |
|
34 * Eric Young (eay@cryptsoft.com)" |
|
35 * The word 'cryptographic' can be left out if the rouines from the library |
|
36 * being used are not cryptographic related :-). |
|
37 * 4. If you include any Windows specific code (or a derivative thereof) from |
|
38 * the apps directory (application code) you must include an acknowledgement: |
|
39 * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" |
|
40 * |
|
41 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND |
|
42 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
43 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
44 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
|
45 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
46 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
47 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
48 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
49 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
50 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
51 * SUCH DAMAGE. |
|
52 * |
|
53 * The licence and distribution terms for any publically available version or |
|
54 * derivative of this code cannot be changed. i.e. this code cannot simply be |
|
55 * copied and put under another distribution licence |
|
56 * [including the GNU Public Licence.] |
|
57 */ |
|
58 |
|
59 #include <stdio.h> |
|
60 #include <errno.h> |
|
61 #include <openssl/crypto.h> |
|
62 #include "cryptlib.h" |
|
63 #include <openssl/bio.h> |
|
64 #include <openssl/stack.h> |
|
65 |
|
66 EXPORT_C BIO *BIO_new(BIO_METHOD *method) |
|
67 { |
|
68 BIO *ret=NULL; |
|
69 |
|
70 ret=(BIO *)OPENSSL_malloc(sizeof(BIO)); |
|
71 if (ret == NULL) |
|
72 { |
|
73 BIOerr(BIO_F_BIO_NEW,ERR_R_MALLOC_FAILURE); |
|
74 return(NULL); |
|
75 } |
|
76 #ifdef SYMBIAN |
|
77 memset(ret,0,sizeof(BIO)); |
|
78 #endif |
|
79 if (!BIO_set(ret,method)) |
|
80 { |
|
81 OPENSSL_free(ret); |
|
82 ret=NULL; |
|
83 } |
|
84 return(ret); |
|
85 } |
|
86 |
|
87 EXPORT_C int BIO_set(BIO *bio, BIO_METHOD *method) |
|
88 { |
|
89 bio->method=method; |
|
90 bio->callback=NULL; |
|
91 bio->cb_arg=NULL; |
|
92 bio->init=0; |
|
93 bio->shutdown=1; |
|
94 bio->flags=0; |
|
95 bio->retry_reason=0; |
|
96 bio->num=0; |
|
97 bio->ptr=NULL; |
|
98 bio->prev_bio=NULL; |
|
99 bio->next_bio=NULL; |
|
100 bio->references=1; |
|
101 bio->num_read=0L; |
|
102 bio->num_write=0L; |
|
103 CRYPTO_new_ex_data(CRYPTO_EX_INDEX_BIO, bio, &bio->ex_data); |
|
104 if (method->create != NULL) |
|
105 if (!method->create(bio)) |
|
106 { |
|
107 CRYPTO_free_ex_data(CRYPTO_EX_INDEX_BIO, bio, |
|
108 &bio->ex_data); |
|
109 return(0); |
|
110 } |
|
111 return(1); |
|
112 } |
|
113 |
|
114 EXPORT_C int BIO_free(BIO *a) |
|
115 { |
|
116 int ret=0,i; |
|
117 |
|
118 if (a == NULL) return(0); |
|
119 |
|
120 i=CRYPTO_add(&a->references,-1,CRYPTO_LOCK_BIO); |
|
121 #ifdef REF_PRINT |
|
122 REF_PRINT("BIO",a); |
|
123 #endif |
|
124 if (i > 0) return(1); |
|
125 #ifdef REF_CHECK |
|
126 if (i < 0) |
|
127 { |
|
128 fprintf(stderr,"BIO_free, bad reference count\n"); |
|
129 abort(); |
|
130 } |
|
131 #endif |
|
132 if ((a->callback != NULL) && |
|
133 ((i=(int)a->callback(a,BIO_CB_FREE,NULL,0,0L,1L)) <= 0)) |
|
134 return(i); |
|
135 |
|
136 CRYPTO_free_ex_data(CRYPTO_EX_INDEX_BIO, a, &a->ex_data); |
|
137 |
|
138 if ((a->method == NULL) || (a->method->destroy == NULL)) return(1); |
|
139 ret=a->method->destroy(a); |
|
140 OPENSSL_free(a); |
|
141 return(1); |
|
142 } |
|
143 |
|
144 EXPORT_C void BIO_vfree(BIO *a) |
|
145 { BIO_free(a); } |
|
146 |
|
147 EXPORT_C void BIO_clear_flags(BIO *b, int flags) |
|
148 { |
|
149 b->flags &= ~flags; |
|
150 } |
|
151 |
|
152 EXPORT_C int BIO_test_flags(const BIO *b, int flags) |
|
153 { |
|
154 return (b->flags & flags); |
|
155 } |
|
156 |
|
157 EXPORT_C void BIO_set_flags(BIO *b, int flags) |
|
158 { |
|
159 b->flags |= flags; |
|
160 } |
|
161 |
|
162 EXPORT_C long (*BIO_get_callback(const BIO *b))(struct bio_st *,int,const char *,int, long,long) |
|
163 { |
|
164 return b->callback; |
|
165 } |
|
166 |
|
167 EXPORT_C void BIO_set_callback(BIO *b, long (*cb)(struct bio_st *,int,const char *,int, long,long)) |
|
168 { |
|
169 b->callback = cb; |
|
170 } |
|
171 |
|
172 EXPORT_C void BIO_set_callback_arg(BIO *b, char *arg) |
|
173 { |
|
174 b->cb_arg = arg; |
|
175 } |
|
176 |
|
177 EXPORT_C char * BIO_get_callback_arg(const BIO *b) |
|
178 { |
|
179 return b->cb_arg; |
|
180 } |
|
181 |
|
182 EXPORT_C const char * BIO_method_name(const BIO *b) |
|
183 { |
|
184 return b->method->name; |
|
185 } |
|
186 |
|
187 EXPORT_C int BIO_method_type(const BIO *b) |
|
188 { |
|
189 return b->method->type; |
|
190 } |
|
191 EXPORT_C int BIO_read(BIO *b, void *out, int outl) |
|
192 { |
|
193 int i; |
|
194 long (*cb)(BIO *,int,const char *,int,long,long); |
|
195 |
|
196 if ((b == NULL) || (b->method == NULL) || (b->method->bread == NULL)) |
|
197 { |
|
198 BIOerr(BIO_F_BIO_READ,BIO_R_UNSUPPORTED_METHOD); |
|
199 return(-2); |
|
200 } |
|
201 |
|
202 cb=b->callback; |
|
203 if ((cb != NULL) && |
|
204 ((i=(int)cb(b,BIO_CB_READ,out,outl,0L,1L)) <= 0)) |
|
205 return(i); |
|
206 |
|
207 if (!b->init) |
|
208 { |
|
209 BIOerr(BIO_F_BIO_READ,BIO_R_UNINITIALIZED); |
|
210 return(-2); |
|
211 } |
|
212 |
|
213 i=b->method->bread(b,out,outl); |
|
214 |
|
215 if (i > 0) b->num_read+=(unsigned long)i; |
|
216 |
|
217 if (cb != NULL) |
|
218 i=(int)cb(b,BIO_CB_READ|BIO_CB_RETURN,out,outl, |
|
219 0L,(long)i); |
|
220 return(i); |
|
221 } |
|
222 |
|
223 EXPORT_C int BIO_write(BIO *b, const void *in, int inl) |
|
224 { |
|
225 int i; |
|
226 long (*cb)(BIO *,int,const char *,int,long,long); |
|
227 |
|
228 if (b == NULL) |
|
229 return(0); |
|
230 |
|
231 cb=b->callback; |
|
232 if ((b->method == NULL) || (b->method->bwrite == NULL)) |
|
233 { |
|
234 BIOerr(BIO_F_BIO_WRITE,BIO_R_UNSUPPORTED_METHOD); |
|
235 return(-2); |
|
236 } |
|
237 |
|
238 if ((cb != NULL) && |
|
239 ((i=(int)cb(b,BIO_CB_WRITE,in,inl,0L,1L)) <= 0)) |
|
240 return(i); |
|
241 |
|
242 if (!b->init) |
|
243 { |
|
244 BIOerr(BIO_F_BIO_WRITE,BIO_R_UNINITIALIZED); |
|
245 return(-2); |
|
246 } |
|
247 |
|
248 i=b->method->bwrite(b,in,inl); |
|
249 |
|
250 if (i > 0) b->num_write+=(unsigned long)i; |
|
251 |
|
252 if (cb != NULL) |
|
253 i=(int)cb(b,BIO_CB_WRITE|BIO_CB_RETURN,in,inl, |
|
254 0L,(long)i); |
|
255 return(i); |
|
256 } |
|
257 |
|
258 EXPORT_C int BIO_puts(BIO *b, const char *in) |
|
259 { |
|
260 int i; |
|
261 long (*cb)(BIO *,int,const char *,int,long,long); |
|
262 |
|
263 if ((b == NULL) || (b->method == NULL) || (b->method->bputs == NULL)) |
|
264 { |
|
265 BIOerr(BIO_F_BIO_PUTS,BIO_R_UNSUPPORTED_METHOD); |
|
266 return(-2); |
|
267 } |
|
268 |
|
269 cb=b->callback; |
|
270 |
|
271 if ((cb != NULL) && |
|
272 ((i=(int)cb(b,BIO_CB_PUTS,in,0,0L,1L)) <= 0)) |
|
273 return(i); |
|
274 |
|
275 if (!b->init) |
|
276 { |
|
277 BIOerr(BIO_F_BIO_PUTS,BIO_R_UNINITIALIZED); |
|
278 return(-2); |
|
279 } |
|
280 |
|
281 i=b->method->bputs(b,in); |
|
282 |
|
283 if (i > 0) b->num_write+=(unsigned long)i; |
|
284 |
|
285 if (cb != NULL) |
|
286 i=(int)cb(b,BIO_CB_PUTS|BIO_CB_RETURN,in,0, |
|
287 0L,(long)i); |
|
288 return(i); |
|
289 } |
|
290 |
|
291 EXPORT_C int BIO_gets(BIO *b, char *in, int inl) |
|
292 { |
|
293 int i; |
|
294 long (*cb)(BIO *,int,const char *,int,long,long); |
|
295 |
|
296 if ((b == NULL) || (b->method == NULL) || (b->method->bgets == NULL)) |
|
297 { |
|
298 BIOerr(BIO_F_BIO_GETS,BIO_R_UNSUPPORTED_METHOD); |
|
299 return(-2); |
|
300 } |
|
301 |
|
302 cb=b->callback; |
|
303 |
|
304 if ((cb != NULL) && |
|
305 ((i=(int)cb(b,BIO_CB_GETS,in,inl,0L,1L)) <= 0)) |
|
306 return(i); |
|
307 |
|
308 if (!b->init) |
|
309 { |
|
310 BIOerr(BIO_F_BIO_GETS,BIO_R_UNINITIALIZED); |
|
311 return(-2); |
|
312 } |
|
313 |
|
314 i=b->method->bgets(b,in,inl); |
|
315 |
|
316 if (cb != NULL) |
|
317 i=(int)cb(b,BIO_CB_GETS|BIO_CB_RETURN,in,inl, |
|
318 0L,(long)i); |
|
319 return(i); |
|
320 } |
|
321 |
|
322 EXPORT_C int BIO_indent(BIO *b,int indent,int max) |
|
323 { |
|
324 if(indent < 0) |
|
325 indent=0; |
|
326 if(indent > max) |
|
327 indent=max; |
|
328 while(indent--) |
|
329 if(BIO_puts(b," ") != 1) |
|
330 return 0; |
|
331 return 1; |
|
332 } |
|
333 |
|
334 EXPORT_C long BIO_int_ctrl(BIO *b, int cmd, long larg, int iarg) |
|
335 { |
|
336 int i; |
|
337 |
|
338 i=iarg; |
|
339 return(BIO_ctrl(b,cmd,larg,(char *)&i)); |
|
340 } |
|
341 |
|
342 EXPORT_C char *BIO_ptr_ctrl(BIO *b, int cmd, long larg) |
|
343 { |
|
344 char *p=NULL; |
|
345 |
|
346 if (BIO_ctrl(b,cmd,larg,(char *)&p) <= 0) |
|
347 return(NULL); |
|
348 else |
|
349 return(p); |
|
350 } |
|
351 |
|
352 EXPORT_C long BIO_ctrl(BIO *b, int cmd, long larg, void *parg) |
|
353 { |
|
354 long ret; |
|
355 long (*cb)(BIO *,int,const char *,int,long,long); |
|
356 |
|
357 if (b == NULL) return(0); |
|
358 |
|
359 if ((b->method == NULL) || (b->method->ctrl == NULL)) |
|
360 { |
|
361 BIOerr(BIO_F_BIO_CTRL,BIO_R_UNSUPPORTED_METHOD); |
|
362 return(-2); |
|
363 } |
|
364 |
|
365 cb=b->callback; |
|
366 |
|
367 if ((cb != NULL) && |
|
368 ((ret=cb(b,BIO_CB_CTRL,parg,cmd,larg,1L)) <= 0)) |
|
369 return(ret); |
|
370 |
|
371 ret=b->method->ctrl(b,cmd,larg,parg); |
|
372 |
|
373 if (cb != NULL) |
|
374 ret=cb(b,BIO_CB_CTRL|BIO_CB_RETURN,parg,cmd, |
|
375 larg,ret); |
|
376 return(ret); |
|
377 } |
|
378 |
|
379 EXPORT_C long BIO_callback_ctrl(BIO *b, int cmd, void (*fp)(struct bio_st *, int, const char *, int, long, long)) |
|
380 { |
|
381 long ret; |
|
382 long (*cb)(BIO *,int,const char *,int,long,long); |
|
383 |
|
384 if (b == NULL) return(0); |
|
385 |
|
386 if ((b->method == NULL) || (b->method->callback_ctrl == NULL)) |
|
387 { |
|
388 BIOerr(BIO_F_BIO_CALLBACK_CTRL,BIO_R_UNSUPPORTED_METHOD); |
|
389 return(-2); |
|
390 } |
|
391 |
|
392 cb=b->callback; |
|
393 |
|
394 if ((cb != NULL) && |
|
395 ((ret=cb(b,BIO_CB_CTRL,(void *)&fp,cmd,0,1L)) <= 0)) |
|
396 return(ret); |
|
397 |
|
398 ret=b->method->callback_ctrl(b,cmd,fp); |
|
399 |
|
400 if (cb != NULL) |
|
401 ret=cb(b,BIO_CB_CTRL|BIO_CB_RETURN,(void *)&fp,cmd, |
|
402 0,ret); |
|
403 return(ret); |
|
404 } |
|
405 |
|
406 /* It is unfortunate to duplicate in functions what the BIO_(w)pending macros |
|
407 * do; but those macros have inappropriate return type, and for interfacing |
|
408 * from other programming languages, C macros aren't much of a help anyway. */ |
|
409 EXPORT_C size_t BIO_ctrl_pending(BIO *bio) |
|
410 { |
|
411 return BIO_ctrl(bio, BIO_CTRL_PENDING, 0, NULL); |
|
412 } |
|
413 |
|
414 EXPORT_C size_t BIO_ctrl_wpending(BIO *bio) |
|
415 { |
|
416 return BIO_ctrl(bio, BIO_CTRL_WPENDING, 0, NULL); |
|
417 } |
|
418 |
|
419 |
|
420 /* put the 'bio' on the end of b's list of operators */ |
|
421 EXPORT_C BIO *BIO_push(BIO *b, BIO *bio) |
|
422 { |
|
423 BIO *lb; |
|
424 |
|
425 if (b == NULL) return(bio); |
|
426 lb=b; |
|
427 while (lb->next_bio != NULL) |
|
428 lb=lb->next_bio; |
|
429 lb->next_bio=bio; |
|
430 if (bio != NULL) |
|
431 bio->prev_bio=lb; |
|
432 /* called to do internal processing */ |
|
433 BIO_ctrl(b,BIO_CTRL_PUSH,0,NULL); |
|
434 return(b); |
|
435 } |
|
436 |
|
437 /* Remove the first and return the rest */ |
|
438 EXPORT_C BIO *BIO_pop(BIO *b) |
|
439 { |
|
440 BIO *ret; |
|
441 |
|
442 if (b == NULL) return(NULL); |
|
443 ret=b->next_bio; |
|
444 |
|
445 BIO_ctrl(b,BIO_CTRL_POP,0,NULL); |
|
446 |
|
447 if (b->prev_bio != NULL) |
|
448 b->prev_bio->next_bio=b->next_bio; |
|
449 if (b->next_bio != NULL) |
|
450 b->next_bio->prev_bio=b->prev_bio; |
|
451 |
|
452 b->next_bio=NULL; |
|
453 b->prev_bio=NULL; |
|
454 return(ret); |
|
455 } |
|
456 |
|
457 EXPORT_C BIO *BIO_get_retry_BIO(BIO *bio, int *reason) |
|
458 { |
|
459 BIO *b,*last; |
|
460 |
|
461 b=last=bio; |
|
462 for (;;) |
|
463 { |
|
464 if (!BIO_should_retry(b)) break; |
|
465 last=b; |
|
466 b=b->next_bio; |
|
467 if (b == NULL) break; |
|
468 } |
|
469 if (reason != NULL) *reason=last->retry_reason; |
|
470 return(last); |
|
471 } |
|
472 |
|
473 EXPORT_C int BIO_get_retry_reason(BIO *bio) |
|
474 { |
|
475 return(bio->retry_reason); |
|
476 } |
|
477 |
|
478 EXPORT_C BIO *BIO_find_type(BIO *bio, int type) |
|
479 { |
|
480 int mt,mask; |
|
481 |
|
482 if(!bio) return NULL; |
|
483 mask=type&0xff; |
|
484 do { |
|
485 if (bio->method != NULL) |
|
486 { |
|
487 mt=bio->method->type; |
|
488 |
|
489 if (!mask) |
|
490 { |
|
491 if (mt & type) return(bio); |
|
492 } |
|
493 else if (mt == type) |
|
494 return(bio); |
|
495 } |
|
496 bio=bio->next_bio; |
|
497 } while (bio != NULL); |
|
498 return(NULL); |
|
499 } |
|
500 |
|
501 EXPORT_C BIO *BIO_next(BIO *b) |
|
502 { |
|
503 if(!b) return NULL; |
|
504 return b->next_bio; |
|
505 } |
|
506 |
|
507 EXPORT_C void BIO_free_all(BIO *bio) |
|
508 { |
|
509 BIO *b; |
|
510 int ref; |
|
511 |
|
512 while (bio != NULL) |
|
513 { |
|
514 b=bio; |
|
515 ref=b->references; |
|
516 bio=bio->next_bio; |
|
517 BIO_free(b); |
|
518 /* Since ref count > 1, don't free anyone else. */ |
|
519 if (ref > 1) break; |
|
520 } |
|
521 } |
|
522 |
|
523 EXPORT_C BIO *BIO_dup_chain(BIO *in) |
|
524 { |
|
525 BIO *ret=NULL,*eoc=NULL,*bio,*new; |
|
526 |
|
527 for (bio=in; bio != NULL; bio=bio->next_bio) |
|
528 { |
|
529 if ((new=BIO_new(bio->method)) == NULL) goto err; |
|
530 new->callback=bio->callback; |
|
531 new->cb_arg=bio->cb_arg; |
|
532 new->init=bio->init; |
|
533 new->shutdown=bio->shutdown; |
|
534 new->flags=bio->flags; |
|
535 |
|
536 /* This will let SSL_s_sock() work with stdin/stdout */ |
|
537 new->num=bio->num; |
|
538 |
|
539 if (!BIO_dup_state(bio,(char *)new)) |
|
540 { |
|
541 BIO_free(new); |
|
542 goto err; |
|
543 } |
|
544 |
|
545 /* copy app data */ |
|
546 if (!CRYPTO_dup_ex_data(CRYPTO_EX_INDEX_BIO, &new->ex_data, |
|
547 &bio->ex_data)) |
|
548 goto err; |
|
549 |
|
550 if (ret == NULL) |
|
551 { |
|
552 eoc=new; |
|
553 ret=eoc; |
|
554 } |
|
555 else |
|
556 { |
|
557 BIO_push(eoc,new); |
|
558 eoc=new; |
|
559 } |
|
560 } |
|
561 return(ret); |
|
562 err: |
|
563 if (ret != NULL) |
|
564 BIO_free(ret); |
|
565 return(NULL); |
|
566 } |
|
567 |
|
568 EXPORT_C void BIO_copy_next_retry(BIO *b) |
|
569 { |
|
570 BIO_set_flags(b,BIO_get_retry_flags(b->next_bio)); |
|
571 b->retry_reason=b->next_bio->retry_reason; |
|
572 } |
|
573 |
|
574 EXPORT_C int BIO_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, |
|
575 CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func) |
|
576 { |
|
577 return CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_BIO, argl, argp, |
|
578 new_func, dup_func, free_func); |
|
579 } |
|
580 |
|
581 EXPORT_C int BIO_set_ex_data(BIO *bio, int idx, void *data) |
|
582 { |
|
583 return(CRYPTO_set_ex_data(&(bio->ex_data),idx,data)); |
|
584 } |
|
585 |
|
586 EXPORT_C void *BIO_get_ex_data(BIO *bio, int idx) |
|
587 { |
|
588 return(CRYPTO_get_ex_data(&(bio->ex_data),idx)); |
|
589 } |
|
590 |
|
591 EXPORT_C unsigned long BIO_number_read(BIO *bio) |
|
592 { |
|
593 if(bio) return bio->num_read; |
|
594 return 0; |
|
595 } |
|
596 |
|
597 EXPORT_C unsigned long BIO_number_written(BIO *bio) |
|
598 { |
|
599 if(bio) return bio->num_write; |
|
600 return 0; |
|
601 } |
|
602 |
|
603 IMPLEMENT_STACK_OF(BIO) |