xref: /iperf/src/iperf_auth.c (revision e919e8c2)
1a51045deSralcini /*
206e3f08dSBruce A. Mah  * iperf, Copyright (c) 2014-2020, The Regents of the University of
3a51045deSralcini  * California, through Lawrence Berkeley National Laboratory (subject
4a51045deSralcini  * to receipt of any required approvals from the U.S. Dept. of
5a51045deSralcini  * Energy).  All rights reserved.
6a51045deSralcini  *
7a51045deSralcini  * If you have questions about your rights to use or distribute this
8a51045deSralcini  * software, please contact Berkeley Lab's Technology Transfer
9a51045deSralcini  * Department at [email protected].
10a51045deSralcini  *
11a51045deSralcini  * NOTICE.  This software is owned by the U.S. Department of Energy.
12a51045deSralcini  * As such, the U.S. Government has been granted for itself and others
13a51045deSralcini  * acting on its behalf a paid-up, nonexclusive, irrevocable,
14a51045deSralcini  * worldwide license in the Software to reproduce, prepare derivative
15a51045deSralcini  * works, and perform publicly and display publicly.  Beginning five
16a51045deSralcini  * (5) years after the date permission to assert copyright is obtained
17a51045deSralcini  * from the U.S. Department of Energy, and subject to any subsequent
18a51045deSralcini  * five (5) year renewals, the U.S. Government is granted for itself
19a51045deSralcini  * and others acting on its behalf a paid-up, nonexclusive,
20a51045deSralcini  * irrevocable, worldwide license in the Software to reproduce,
21a51045deSralcini  * prepare derivative works, distribute copies to the public, perform
22a51045deSralcini  * publicly and display publicly, and to permit others to do so.
23a51045deSralcini  *
24a51045deSralcini  * This code is distributed under a BSD style license, see the LICENSE file
25a51045deSralcini  * for complete information.
26a51045deSralcini  */
27a51045deSralcini 
28a51045deSralcini #include "iperf_config.h"
29a51045deSralcini 
30a51045deSralcini #include <string.h>
31a51045deSralcini #include <assert.h>
32a51045deSralcini #include <time.h>
33a51045deSralcini #include <sys/types.h>
34c0225583SBruce A. Mah /* FreeBSD needs _WITH_GETLINE to enable the getline() declaration */
35c0225583SBruce A. Mah #define _WITH_GETLINE
36a51045deSralcini #include <stdio.h>
37a51045deSralcini #include <termios.h>
382a1309faSA. Wilcox #include <inttypes.h>
392a1309faSA. Wilcox #include <stdint.h>
40a51045deSralcini 
41a51045deSralcini #if defined(HAVE_SSL)
42a51045deSralcini 
43517ad224SPhilip Prindeville #include <openssl/rsa.h>
44a51045deSralcini #include <openssl/bio.h>
45a51045deSralcini #include <openssl/pem.h>
46a51045deSralcini #include <openssl/sha.h>
47a51045deSralcini #include <openssl/buffer.h>
4806e3f08dSBruce A. Mah #include <openssl/err.h>
4906e3f08dSBruce A. Mah 
502a1309faSA. Wilcox const char *auth_text_format = "user: %s\npwd:  %s\nts:   %"PRId64;
51a51045deSralcini 
sha256(const char * string,char outputBuffer[65])52a51045deSralcini void sha256(const char *string, char outputBuffer[65])
53a51045deSralcini {
54a51045deSralcini     unsigned char hash[SHA256_DIGEST_LENGTH];
55a51045deSralcini     SHA256_CTX sha256;
56a51045deSralcini     SHA256_Init(&sha256);
57a51045deSralcini     SHA256_Update(&sha256, string, strlen(string));
58a51045deSralcini     SHA256_Final(hash, &sha256);
59a51045deSralcini     int i = 0;
60a51045deSralcini     for(i = 0; i < SHA256_DIGEST_LENGTH; i++)
61a51045deSralcini     {
62a51045deSralcini         sprintf(outputBuffer + (i * 2), "%02x", hash[i]);
63a51045deSralcini     }
64a51045deSralcini     outputBuffer[64] = 0;
65a51045deSralcini }
66a51045deSralcini 
check_authentication(const char * username,const char * password,const time_t ts,const char * filename,int skew_threshold)67bd143779Sralcini int check_authentication(const char *username, const char *password, const time_t ts, const char *filename, int skew_threshold){
68a51045deSralcini     time_t t = time(NULL);
69a51045deSralcini     time_t utc_seconds = mktime(localtime(&t));
70bd143779Sralcini     if ( (utc_seconds - ts) > skew_threshold || (utc_seconds - ts) < -skew_threshold ) {
71a51045deSralcini         return 1;
72a51045deSralcini     }
73a51045deSralcini 
74a51045deSralcini     char passwordHash[65];
75a51045deSralcini     char salted[strlen(username) + strlen(password) + 3];
76a51045deSralcini     sprintf(salted, "{%s}%s", username, password);
77a51045deSralcini     sha256(&salted[0], passwordHash);
78a51045deSralcini 
79a51045deSralcini     char *s_username, *s_password;
80a51045deSralcini     int i;
81a51045deSralcini     FILE *ptr_file;
82a51045deSralcini     char buf[1024];
83a51045deSralcini 
84a51045deSralcini     ptr_file =fopen(filename,"r");
85a51045deSralcini     if (!ptr_file)
86a51045deSralcini         return 2;
87a51045deSralcini 
88a51045deSralcini     while (fgets(buf,1024, ptr_file)){
89a51045deSralcini         //strip the \n or \r\n chars
90a51045deSralcini         for (i = 0; buf[i] != '\0'; i++){
91a51045deSralcini             if (buf[i] == '\n' || buf[i] == '\r'){
92a51045deSralcini                 buf[i] = '\0';
93a51045deSralcini                 break;
94a51045deSralcini             }
95a51045deSralcini         }
96a51045deSralcini         //skip empty / not completed / comment lines
97a51045deSralcini         if (strlen(buf) == 0 || strchr(buf, ',') == NULL || buf[0] == '#'){
98a51045deSralcini             continue;
99a51045deSralcini         }
100a51045deSralcini         s_username = strtok(buf, ",");
101a51045deSralcini         s_password = strtok(NULL, ",");
102a51045deSralcini         if (strcmp( username, s_username ) == 0 && strcmp( passwordHash, s_password ) == 0){
103c4bd56f3SBruce A. Mah             fclose(ptr_file);
104a51045deSralcini             return 0;
105a51045deSralcini         }
106a51045deSralcini     }
1078740c8cfSBruce A. Mah     fclose(ptr_file);
108a51045deSralcini     return 3;
109a51045deSralcini }
110a51045deSralcini 
111a51045deSralcini 
Base64Encode(const unsigned char * buffer,const size_t length,char ** b64text)112e28f12c7Sralcini int Base64Encode(const unsigned char* buffer, const size_t length, char** b64text) { //Encodes a binary safe base 64 string
113a51045deSralcini     BIO *bio, *b64;
114a51045deSralcini     BUF_MEM *bufferPtr;
115a51045deSralcini 
116a51045deSralcini     b64 = BIO_new(BIO_f_base64());
117a51045deSralcini     bio = BIO_new(BIO_s_mem());
118a51045deSralcini     bio = BIO_push(b64, bio);
119a51045deSralcini 
120a51045deSralcini     BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Ignore newlines - write everything in one line
121a51045deSralcini     BIO_write(bio, buffer, length);
122a51045deSralcini     BIO_flush(bio);
123a51045deSralcini     BIO_get_mem_ptr(bio, &bufferPtr);
124c4bd56f3SBruce A. Mah     *b64text = strndup( (*bufferPtr).data, (*bufferPtr).length );
125a51045deSralcini     BIO_free_all(bio);
126a51045deSralcini 
127a51045deSralcini     return (0); //success
128a51045deSralcini }
129a51045deSralcini 
calcDecodeLength(const char * b64input)130a51045deSralcini size_t calcDecodeLength(const char* b64input) { //Calculates the length of a decoded string
131a51045deSralcini     size_t len = strlen(b64input), padding = 0;
132a51045deSralcini     if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
133a51045deSralcini         padding = 2;
134a51045deSralcini     else if (b64input[len-1] == '=') //last char is =
135a51045deSralcini         padding = 1;
136a51045deSralcini 
137a51045deSralcini     return (len*3)/4 - padding;
138a51045deSralcini }
139a51045deSralcini 
Base64Decode(const char * b64message,unsigned char ** buffer,size_t * length)140e28f12c7Sralcini int Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) { //Decodes a base64 encoded string
141a51045deSralcini     BIO *bio, *b64;
142a51045deSralcini 
143a51045deSralcini     int decodeLen = calcDecodeLength(b64message);
144a51045deSralcini     *buffer = (unsigned char*)malloc(decodeLen + 1);
145a51045deSralcini     (*buffer)[decodeLen] = '\0';
146a51045deSralcini 
147a51045deSralcini     bio = BIO_new_mem_buf(b64message, -1);
148a51045deSralcini     b64 = BIO_new(BIO_f_base64());
149a51045deSralcini     bio = BIO_push(b64, bio);
150a51045deSralcini 
151a51045deSralcini     BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer
152a51045deSralcini     *length = BIO_read(bio, *buffer, strlen(b64message));
153a51045deSralcini     assert(*length == decodeLen); //length should equal decodeLen, else something went horribly wrong
154a51045deSralcini     BIO_free_all(bio);
155a51045deSralcini 
156a51045deSralcini     return (0); //success
157a51045deSralcini }
158a51045deSralcini 
load_pubkey_from_file(const char * file)159e28f12c7Sralcini EVP_PKEY *load_pubkey_from_file(const char *file) {
160a51045deSralcini     BIO *key = NULL;
161a51045deSralcini     EVP_PKEY *pkey = NULL;
162a51045deSralcini 
163c4bd56f3SBruce A. Mah     if (file) {
164a51045deSralcini       key = BIO_new_file(file, "r");
165a51045deSralcini       pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL);
166a51045deSralcini 
167a51045deSralcini       BIO_free(key);
168c4bd56f3SBruce A. Mah     }
169a51045deSralcini     return (pkey);
170a51045deSralcini }
171a51045deSralcini 
load_pubkey_from_base64(const char * buffer)172e28f12c7Sralcini EVP_PKEY *load_pubkey_from_base64(const char *buffer) {
173e28f12c7Sralcini     unsigned char *key = NULL;
174e28f12c7Sralcini     size_t key_len;
175e28f12c7Sralcini     Base64Decode(buffer, &key, &key_len);
176e28f12c7Sralcini 
177e28f12c7Sralcini     BIO* bio = BIO_new(BIO_s_mem());
178e28f12c7Sralcini     BIO_write(bio, key, key_len);
17980b7c7b2SBruce A. Mah     free(key);
180e28f12c7Sralcini     EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
181bb115306SOleh Yudin     BIO_free(bio);
182e28f12c7Sralcini     return (pkey);
183e28f12c7Sralcini }
184e28f12c7Sralcini 
load_privkey_from_file(const char * file)185e28f12c7Sralcini EVP_PKEY *load_privkey_from_file(const char *file) {
186a51045deSralcini     BIO *key = NULL;
187a51045deSralcini     EVP_PKEY *pkey = NULL;
188a51045deSralcini 
189c4bd56f3SBruce A. Mah     if (file) {
190a51045deSralcini       key = BIO_new_file(file, "r");
191a51045deSralcini       pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
192a51045deSralcini 
193a51045deSralcini       BIO_free(key);
194c4bd56f3SBruce A. Mah     }
195a51045deSralcini     return (pkey);
196a51045deSralcini }
197a51045deSralcini 
load_privkey_from_base64(const char * buffer)19822da02dcSAllen Flickinger EVP_PKEY *load_privkey_from_base64(const char *buffer) {
19922da02dcSAllen Flickinger     unsigned char *key = NULL;
20022da02dcSAllen Flickinger     size_t key_len;
20122da02dcSAllen Flickinger     Base64Decode(buffer, &key, &key_len);
20222da02dcSAllen Flickinger 
20322da02dcSAllen Flickinger     BIO* bio = BIO_new(BIO_s_mem());
20422da02dcSAllen Flickinger     BIO_write(bio, key, key_len);
20580b7c7b2SBruce A. Mah     free(key);
20622da02dcSAllen Flickinger     EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
207bb115306SOleh Yudin     BIO_free(bio);
20822da02dcSAllen Flickinger     return (pkey);
20922da02dcSAllen Flickinger }
210a51045deSralcini 
test_load_pubkey_from_file(const char * file)211e28f12c7Sralcini int test_load_pubkey_from_file(const char *file){
212e28f12c7Sralcini     EVP_PKEY *key = load_pubkey_from_file(file);
213a51045deSralcini     if (key == NULL){
214a51045deSralcini         return -1;
215a51045deSralcini     }
216a51045deSralcini     EVP_PKEY_free(key);
217a51045deSralcini     return 0;
218a51045deSralcini }
219a51045deSralcini 
test_load_private_key_from_file(const char * file)220e28f12c7Sralcini int test_load_private_key_from_file(const char *file){
221e28f12c7Sralcini     EVP_PKEY *key = load_privkey_from_file(file);
222a51045deSralcini     if (key == NULL){
223a51045deSralcini         return -1;
224a51045deSralcini     }
225a51045deSralcini     EVP_PKEY_free(key);
226a51045deSralcini     return 0;
227a51045deSralcini }
228a51045deSralcini 
encrypt_rsa_message(const char * plaintext,EVP_PKEY * public_key,unsigned char ** encryptedtext)229e28f12c7Sralcini int encrypt_rsa_message(const char *plaintext, EVP_PKEY *public_key, unsigned char **encryptedtext) {
230a51045deSralcini     RSA *rsa = NULL;
231a51045deSralcini     unsigned char *rsa_buffer = NULL, pad = RSA_PKCS1_PADDING;
232a51045deSralcini     int keysize, encryptedtext_len, rsa_buffer_len;
233a51045deSralcini 
234a51045deSralcini     rsa = EVP_PKEY_get1_RSA(public_key);
235a51045deSralcini     keysize = RSA_size(rsa);
236e28f12c7Sralcini 
237a51045deSralcini     rsa_buffer  = OPENSSL_malloc(keysize * 2);
238a51045deSralcini     *encryptedtext = (unsigned char*)OPENSSL_malloc(keysize);
239a51045deSralcini 
240a51045deSralcini     BIO *bioBuff   = BIO_new_mem_buf((void*)plaintext, (int)strlen(plaintext));
241a51045deSralcini     rsa_buffer_len = BIO_read(bioBuff, rsa_buffer, keysize * 2);
242a51045deSralcini     encryptedtext_len = RSA_public_encrypt(rsa_buffer_len, rsa_buffer, *encryptedtext, rsa, pad);
243a51045deSralcini 
244a51045deSralcini     RSA_free(rsa);
245a51045deSralcini     OPENSSL_free(rsa_buffer);
246c4bd56f3SBruce A. Mah     BIO_free(bioBuff);
247a51045deSralcini 
24806e3f08dSBruce A. Mah     if (encryptedtext_len < 0) {
249*50d6cce6Sa1346054       /* We probably shouldn't be printing stuff like this */
25006e3f08dSBruce A. Mah       fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL));
25106e3f08dSBruce A. Mah     }
25206e3f08dSBruce A. Mah 
253a51045deSralcini     return encryptedtext_len;
254a51045deSralcini }
255a51045deSralcini 
decrypt_rsa_message(const unsigned char * encryptedtext,const int encryptedtext_len,EVP_PKEY * private_key,unsigned char ** plaintext)256e28f12c7Sralcini int decrypt_rsa_message(const unsigned char *encryptedtext, const int encryptedtext_len, EVP_PKEY *private_key, unsigned char **plaintext) {
257a51045deSralcini     RSA *rsa = NULL;
258a51045deSralcini     unsigned char *rsa_buffer = NULL, pad = RSA_PKCS1_PADDING;
259a51045deSralcini     int plaintext_len, rsa_buffer_len, keysize;
260a51045deSralcini 
261a51045deSralcini     rsa = EVP_PKEY_get1_RSA(private_key);
262a51045deSralcini 
263a51045deSralcini     keysize = RSA_size(rsa);
264a51045deSralcini     rsa_buffer  = OPENSSL_malloc(keysize * 2);
265a51045deSralcini     *plaintext = (unsigned char*)OPENSSL_malloc(keysize);
266a51045deSralcini 
267a51045deSralcini     BIO *bioBuff   = BIO_new_mem_buf((void*)encryptedtext, encryptedtext_len);
268a51045deSralcini     rsa_buffer_len = BIO_read(bioBuff, rsa_buffer, keysize * 2);
269a51045deSralcini     plaintext_len = RSA_private_decrypt(rsa_buffer_len, rsa_buffer, *plaintext, rsa, pad);
270a51045deSralcini 
271a51045deSralcini     RSA_free(rsa);
272a51045deSralcini     OPENSSL_free(rsa_buffer);
273c4bd56f3SBruce A. Mah     BIO_free(bioBuff);
274a51045deSralcini 
27506e3f08dSBruce A. Mah     if (plaintext_len < 0) {
276*50d6cce6Sa1346054       /* We probably shouldn't be printing stuff like this */
27706e3f08dSBruce A. Mah       fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL));
27806e3f08dSBruce A. Mah     }
27906e3f08dSBruce A. Mah 
280a51045deSralcini     return plaintext_len;
281a51045deSralcini }
282a51045deSralcini 
encode_auth_setting(const char * username,const char * password,EVP_PKEY * public_key,char ** authtoken)283e28f12c7Sralcini int encode_auth_setting(const char *username, const char *password, EVP_PKEY *public_key, char **authtoken){
284a51045deSralcini     time_t t = time(NULL);
285a51045deSralcini     time_t utc_seconds = mktime(localtime(&t));
28606e3f08dSBruce A. Mah 
28706e3f08dSBruce A. Mah     /*
28806e3f08dSBruce A. Mah      * Compute a pessimistic/conservative estimate of storage required.
28906e3f08dSBruce A. Mah      * It's OK to allocate too much storage but too little is bad.
29006e3f08dSBruce A. Mah      */
29106e3f08dSBruce A. Mah     const int text_len = strlen(auth_text_format) + strlen(username) + strlen(password) + 32;
29206e3f08dSBruce A. Mah     char *text = (char *) calloc(text_len, sizeof(char));
29306e3f08dSBruce A. Mah     if (text == NULL) {
29406e3f08dSBruce A. Mah 	return -1;
29506e3f08dSBruce A. Mah     }
2962a1309faSA. Wilcox     snprintf(text, text_len, auth_text_format, username, password, (int64_t)utc_seconds);
29706e3f08dSBruce A. Mah 
298a51045deSralcini     unsigned char *encrypted = NULL;
299a51045deSralcini     int encrypted_len;
300e28f12c7Sralcini     encrypted_len = encrypt_rsa_message(text, public_key, &encrypted);
30199b79f21SBruce A. Mah     free(text);
30206e3f08dSBruce A. Mah     if (encrypted_len < 0) {
30306e3f08dSBruce A. Mah       return -1;
30406e3f08dSBruce A. Mah     }
305a51045deSralcini     Base64Encode(encrypted, encrypted_len, authtoken);
306c4bd56f3SBruce A. Mah     OPENSSL_free(encrypted);
307c4bd56f3SBruce A. Mah 
308a51045deSralcini     return (0); //success
309a51045deSralcini }
310a51045deSralcini 
decode_auth_setting(int enable_debug,const char * authtoken,EVP_PKEY * private_key,char ** username,char ** password,time_t * ts)3113888e044SDavid Bar-On int decode_auth_setting(int enable_debug, const char *authtoken, EVP_PKEY *private_key, char **username, char **password, time_t *ts){
312a51045deSralcini     unsigned char *encrypted_b64 = NULL;
313a51045deSralcini     size_t encrypted_len_b64;
3142a1309faSA. Wilcox     int64_t utc_seconds;
315a51045deSralcini     Base64Decode(authtoken, &encrypted_b64, &encrypted_len_b64);
316a51045deSralcini 
317a51045deSralcini     unsigned char *plaintext = NULL;
318a51045deSralcini     int plaintext_len;
319e28f12c7Sralcini     plaintext_len = decrypt_rsa_message(encrypted_b64, encrypted_len_b64, private_key, &plaintext);
320c4bd56f3SBruce A. Mah     free(encrypted_b64);
32106e3f08dSBruce A. Mah     if (plaintext_len < 0) {
32206e3f08dSBruce A. Mah         return -1;
32306e3f08dSBruce A. Mah     }
32406e3f08dSBruce A. Mah     plaintext[plaintext_len] = '\0';
325a51045deSralcini 
32606e3f08dSBruce A. Mah     char *s_username, *s_password;
32706e3f08dSBruce A. Mah     s_username = (char *) calloc(plaintext_len, sizeof(char));
32806e3f08dSBruce A. Mah     if (s_username == NULL) {
32906e3f08dSBruce A. Mah 	return -1;
33006e3f08dSBruce A. Mah     }
33106e3f08dSBruce A. Mah     s_password = (char *) calloc(plaintext_len, sizeof(char));
33206e3f08dSBruce A. Mah     if (s_password == NULL) {
33306e3f08dSBruce A. Mah 	free(s_username);
33406e3f08dSBruce A. Mah 	return -1;
33506e3f08dSBruce A. Mah     }
33606e3f08dSBruce A. Mah 
3372a1309faSA. Wilcox     int rc = sscanf((char *) plaintext, auth_text_format, s_username, s_password, &utc_seconds);
33806e3f08dSBruce A. Mah     if (rc != 3) {
33906e3f08dSBruce A. Mah 	free(s_password);
34006e3f08dSBruce A. Mah 	free(s_username);
34106e3f08dSBruce A. Mah 	return -1;
34206e3f08dSBruce A. Mah     }
34306e3f08dSBruce A. Mah 
344a51045deSralcini     if (enable_debug) {
345a51045deSralcini         printf("Auth Token Content:\n%s\n", plaintext);
346a51045deSralcini         printf("Auth Token Credentials:\n--> %s %s\n", s_username, s_password);
347a51045deSralcini     }
34806e3f08dSBruce A. Mah     *username = s_username;
34906e3f08dSBruce A. Mah     *password = s_password;
3502a1309faSA. Wilcox     *ts = (time_t)utc_seconds;
351c4bd56f3SBruce A. Mah     OPENSSL_free(plaintext);
352a51045deSralcini     return (0);
353a51045deSralcini }
354a51045deSralcini 
355a51045deSralcini #endif //HAVE_SSL
356a51045deSralcini 
iperf_getpass(char ** lineptr,size_t * n,FILE * stream)357a51045deSralcini ssize_t iperf_getpass (char **lineptr, size_t *n, FILE *stream) {
358a51045deSralcini     struct termios old, new;
359a51045deSralcini     ssize_t nread;
360a51045deSralcini 
361a51045deSralcini     /* Turn echoing off and fail if we can't. */
362a51045deSralcini     if (tcgetattr (fileno (stream), &old) != 0)
363a51045deSralcini         return -1;
364a51045deSralcini     new = old;
365a51045deSralcini     new.c_lflag &= ~ECHO;
366a51045deSralcini     if (tcsetattr (fileno (stream), TCSAFLUSH, &new) != 0)
367a51045deSralcini         return -1;
368a51045deSralcini 
369a51045deSralcini     /* Read the password. */
370a51045deSralcini     printf("Password: ");
371a51045deSralcini     nread = getline (lineptr, n, stream);
372a51045deSralcini 
373a51045deSralcini     /* Restore terminal. */
374a51045deSralcini     (void) tcsetattr (fileno (stream), TCSAFLUSH, &old);
375a51045deSralcini 
376a51045deSralcini     //strip the \n or \r\n chars
377a51045deSralcini     char *buf = *lineptr;
378a51045deSralcini     int i;
379a51045deSralcini     for (i = 0; buf[i] != '\0'; i++){
380a51045deSralcini         if (buf[i] == '\n' || buf[i] == '\r'){
381a51045deSralcini             buf[i] = '\0';
382a51045deSralcini             break;
383a51045deSralcini         }
384a51045deSralcini     }
385a51045deSralcini 
386a51045deSralcini     return nread;
387a51045deSralcini }
388