diff options
Diffstat (limited to 'daemon/ldap.c')
-rw-r--r-- | daemon/ldap.c | 219 |
1 files changed, 123 insertions, 96 deletions
diff --git a/daemon/ldap.c b/daemon/ldap.c index 9a79d43..a5abd97 100644 --- a/daemon/ldap.c +++ b/daemon/ldap.c @@ -69,24 +69,17 @@ typedef struct ldap_context int port; /* Port to connect to LDAP server on */ int scope; /* Scope for filter */ - const char* realm; /* The realm to use in authentication */ - const char* domains; /* Domains for which digest auth is valid */ - int dobind; /* Bind to do simple authentication */ - int cache_max; /* Maximum number of connections at once */ - int cache_timeout; int ldap_max; /* Number of open connections allowed */ int ldap_timeout; /* Maximum amount of time to dedicate to an ldap query */ + ha_options_t* opts; /* Options from httpauthd.c */ + /* Context ----------------------------------------------------------- */ hash_t* cache; /* Some cached records or basic */ LDAP** pool; /* Pool of available connections */ int pool_mark; /* Amount of connections allocated */ - -#ifdef _DEBUG - const char* debug_nonce; -#endif } ldap_context_t; @@ -104,19 +97,12 @@ static const ldap_context_t ldap_defaults = NULL, /* dnmap */ 389, /* port */ LDAP_SCOPE_SUBTREE, /* scope */ - "", /* realm */ - NULL, /* domains */ 1, /* dobind */ - 1000, /* cache_max */ - 30, /* cache_timeout */ 10, /* ldap_max */ 30, /* ldap_timeout */ NULL, /* cache */ NULL, /* pool */ 0 /* pool_mark */ -#ifdef _DEBUG - , NULL /* debug_nonce */ -#endif }; @@ -134,17 +120,17 @@ static int report_ldap(const char* msg, int code) { ASSERT(code != LDAP_SUCCESS); - if(!msg) - msg = "ldap error"; - - ha_messagex(LOG_ERR, "%s: %s", msg, ldap_err2string(code)); - switch(code) { case LDAP_NO_MEMORY: + ha_messagex(LOG_CRIT, "out of memory"); return HA_CRITERROR; default: + if(!msg) + msg = "error"; + + ha_messagex(LOG_ERR, "ldap: %s: %s", msg, ldap_err2string(code)); return HA_FAILED; }; } @@ -155,7 +141,7 @@ static digest_record_t* get_cached_digest(ldap_context_t* ctx, unsigned char* no ASSERT(ctx && nonce); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) return NULL; ha_lock(NULL); @@ -193,7 +179,7 @@ static int save_cached_digest(ldap_context_t* ctx, digest_record_t* rec) ASSERT(ctx && rec); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) { free_hash_object(NULL, rec); return HA_FALSE; @@ -201,7 +187,7 @@ static int save_cached_digest(ldap_context_t* ctx, digest_record_t* rec) ha_lock(NULL); - while(hash_count(ctx->cache) >= ctx->cache_max) + while(hash_count(ctx->cache) >= ctx->opts->cache_max) hash_bump(ctx->cache); r = hash_set(ctx->cache, rec->nonce, rec); @@ -223,12 +209,12 @@ static int add_cached_basic(ldap_context_t* ctx, unsigned char* key) ASSERT(ctx && key); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) return HA_FALSE; ha_lock(NULL); - while(hash_count(ctx->cache) >= ctx->cache_max) + while(hash_count(ctx->cache) >= ctx->opts->cache_max) hash_bump(ctx->cache); r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); @@ -279,7 +265,7 @@ static const char* substitute_params(ldap_context_t* ctx, ha_buffer_t* buf, case 'r': ha_bufjoin(buf); - ha_bufcpy(buf, ctx->realm); + ha_bufcpy(buf, ctx->opts->realm); t++; break; @@ -388,6 +374,7 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha /* Raw binary */ if(bv->bv_len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "ldap: found ha1 in raw binary format"); memcpy(ha1, bv->bv_val, MD5_LEN); return HA_OK; } @@ -400,6 +387,7 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha if(d && len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "ldap: found ha1 in hex encoded format"); memcpy(ha1, d, MD5_LEN); return HA_OK; } @@ -413,6 +401,7 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha if(d && len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "ldap: found ha1 in b64 encoded format"); memcpy(ha1, ha_bufdata(buf), MD5_LEN); return HA_OK; } @@ -483,6 +472,7 @@ static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* en if(strcmp(pw, p) == 0) { + ha_messagex(LOG_DEBUG, "ldap: successful validate against password"); res = HA_OK; break; } @@ -492,7 +482,7 @@ static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* en } if(res == HA_FALSE && unknown) - ha_messagex(LOG_ERR, "LDAP does not contain any compatible passwords for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server does not contain any compatible passwords for user: %s", user); return res; } @@ -516,7 +506,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(ha1s) { - digest_makeha1(key, user, ctx->realm, clearpw); + digest_makeha1(key, user, ctx->opts->realm, clearpw); for(b = ha1s ; *b; b++) { @@ -530,7 +520,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(r == HA_FALSE) { if(first) - ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 digest hash for user: %s", user); first = 0; continue; @@ -538,6 +528,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(memcmp(key, k, MD5_LEN) == 0) { + ha_messagex(LOG_DEBUG, "ldap: successful validate against ha1"); res = HA_OK; break; } @@ -561,6 +552,7 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) /* An open connection in the pool */ if(ctx->pool[i]) { + ha_messagex(LOG_DEBUG, "ldap: using cached connection"); ld = ctx->pool[i]; ctx->pool[i] = NULL; return ld; @@ -569,14 +561,14 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) if(ctx->pool_mark >= ctx->ldap_max) { - ha_messagex(LOG_ERR, "too many open connections to LDAP"); + ha_messagex(LOG_ERR, "ldap: too many open connections"); return NULL; } ld = ldap_init(ctx->servers, ctx->port); if(!ld) { - ha_message(LOG_ERR, "couldn't initialize ldap connection"); + ha_message(LOG_ERR, "ldap: couldn't initialize connection"); return NULL; } @@ -586,13 +578,14 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) ctx->password ? ctx->password : ""); if(r != LDAP_SUCCESS) { - report_ldap("couldn't bind to LDAP server", r); + report_ldap("ldap: couldn't bind to LDAP server", r); ldap_unbind_s(ld); return NULL; } } ctx->pool_mark++; + ha_messagex(LOG_DEBUG, "ldap: opened new connection (total %d)", ctx->pool_mark); return ld; } @@ -621,6 +614,7 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) /* An open connection in the pool */ if(!ctx->pool[i]) { + ha_messagex(LOG_DEBUG, "ldap: caching connection for later use"); ctx->pool[i] = ld; ld = NULL; break; @@ -634,6 +628,7 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) { ldap_unbind_s(ld); ctx->pool_mark--; + ha_messagex(LOG_DEBUG, "ldap: discarding connection (total %d)", ctx->pool_mark); } } @@ -644,6 +639,8 @@ static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, struct timeval tv; const char* filter; const char* attrs[3]; + const char* base; + int scope; int r; ASSERT(ctx && buf && ld && user && dn && entry && result); @@ -667,15 +664,19 @@ static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, tv.tv_sec = ctx->ldap_timeout; tv.tv_usec = 0; - r = ldap_search_st(ld, *dn ? *dn : ctx->base, - *dn ? LDAP_SCOPE_BASE : ctx->scope, - filter, (char**)attrs, 0, &tv, result); + base = *dn ? *dn : ctx->base; + scope = *dn ? LDAP_SCOPE_BASE : ctx->scope; + + ha_messagex(LOG_DEBUG, "ldap: performing search: [ base: %s / scope: %d / filter: %s ]", + base, scope, filter); + + r = ldap_search_st(ld, base, scope, filter, (char**)attrs, 0, &tv, result); if(r != LDAP_SUCCESS) { if(r == LDAP_NO_SUCH_OBJECT) { - ha_messagex(LOG_WARNING, "user not found in LDAP: %s", user); + ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user); return HA_FALSE; } @@ -689,15 +690,18 @@ static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, case 1: *entry = ldap_first_entry(ld, *result); if(!(*dn)) + { *dn = ldap_get_dn(ld, *entry); + ha_messagex(LOG_DEBUG, "ldap: found entry for user: %s", *dn); + } return HA_OK; case 0: - ha_messagex(LOG_WARNING, "user not found in LDAP: %s", user); + ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user); break; default: - ha_messagex(LOG_WARNING, "more than one user found for filter: %s", filter); + ha_messagex(LOG_WARNING, "ldap: more than one user found for filter: %s", filter); break; }; @@ -739,6 +743,8 @@ static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, ret = HA_CRITERROR; goto finally; } + + ha_messagex(LOG_INFO, "ldap: mapped %s to %s", user, dn); } /* Okay now we contact the LDAP server. */ @@ -761,10 +767,11 @@ static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, if(ret != HA_OK) { if(ret == HA_FALSE) - ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 for user: %s", user); } } + ha_messagex(LOG_DEBUG, "ldap: using HA1 from ldap"); ldap_value_free_len(ha1s); goto finally; } @@ -779,17 +786,20 @@ static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, if(t) { - digest_makeha1(rec->ha1, user, ctx->realm, t); + digest_makeha1(rec->ha1, user, ctx->opts->realm, t); ret = HA_OK; } ldap_value_free(pws); if(ret == HA_OK) + { + ha_messagex(LOG_DEBUG, "ldap: using clear password from ldap"); goto finally; + } } - ha_messagex(LOG_ERR, "LDAP contains no cleartext password for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server contains no clear password or HA1 for user: %s", user); finally: @@ -824,6 +834,8 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, /* Check and see if this connection is in the cache */ if(have_cached_basic(ctx, basic.key)) { + ha_messagex(LOG_NOTICE, "ldap: validated basic user against cache: %s", + basic.user); found = 1; ret = HA_OK; goto finally; @@ -832,7 +844,10 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, /* If we have a user name and password */ if(!basic.user || !basic.user[0] || !basic.password || !basic.password[0]) + { + ha_messagex(LOG_NOTICE, "ldap: no valid basic auth info"); goto finally; + } ld = get_ldap_connection(ctx); @@ -857,6 +872,8 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, ret = HA_CRITERROR; goto finally; } + + ha_messagex(LOG_INFO, "ldap: mapped %s to %s", basic.user, dn); } @@ -895,7 +912,9 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, if(r != LDAP_SUCCESS) { if(r == LDAP_INVALID_CREDENTIALS) - ha_messagex(LOG_WARNING, "invalid login for: %s", basic.user); + ha_messagex(LOG_WARNING, "ldap: basic authentication (via bind) failed for user: %s", + basic.user); + else report_ldap("couldn't bind to LDAP server", r); @@ -903,6 +922,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, } /* It worked! */ + ha_messagex(LOG_NOTICE, "ldap: validated basic user using bind: %s", basic.user); found = 1; } @@ -915,10 +935,15 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, ret = validate_ldap_ha1(ctx, ld, entry, buf, basic.user, basic.password); if(ret == HA_OK) + { + ha_messagex(LOG_NOTICE, "ldap: validated basic user password/ha1: %s", basic.user); found = 1; + } else - ha_messagex(LOG_WARNING, "invalid, unreadable or unrecognized password for user: %s", basic.user); + { + ha_messagex(LOG_WARNING, "ldap: invalid, unreadable or unrecognized password for user: %s", basic.user); + } } @@ -952,10 +977,10 @@ static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, ASSERT(ctx && resp && buf); #ifdef _DEBUG - if(ctx->debug_nonce) + if(ctx->opts->digest_debugnonce) { - nonce_str = ctx->debug_nonce; - ha_messagex(LOG_WARNING, "using debug nonce. security non-existant."); + nonce_str = ctx->opts->digest_debugnonce; + ha_messagex(LOG_WARNING, "simple: using debug nonce. security non-existant."); } else #endif @@ -969,7 +994,8 @@ static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, } /* Now generate a message to send */ - header = digest_challenge(buf, nonce_str, ctx->realm, ctx->domains, stale); + header = digest_challenge(buf, nonce_str, ctx->opts->realm, + ctx->opts->digest_domains, stale); if(!header) return HA_CRITERROR; @@ -978,6 +1004,7 @@ static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, resp->code = HA_SERVER_DECLINE; ha_addheader(resp, "WWW-Authenticate", header); + ha_messagex(LOG_DEBUG, "ldap: created digest challenge with nonce: %s", nonce_str); return HA_OK; } @@ -1003,18 +1030,18 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, return r; #ifdef _DEBUG - if(ctx->debug_nonce) + if(ctx->opts->digest_debugnonce) { - if(dg.nonce && strcmp(dg.nonce, ctx->debug_nonce) != 0) + if(dg.nonce && strcmp(dg.nonce, ctx->opts->digest_debugnonce) != 0) { ret = HA_FALSE; resp->code = HA_SERVER_BADREQ; - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + ha_messagex(LOG_WARNING, "ldap: digest response contains invalid nonce"); goto finally; } /* Do a rough hash into the real nonce, for use as a key */ - md5_string(nonce, ctx->debug_nonce); + md5_string(nonce, ctx->opts->digest_debugnonce); /* Debug nonce's never expire */ expiry = time(NULL); @@ -1028,25 +1055,29 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, if(r == HA_FALSE) { resp->code = HA_SERVER_BADREQ; - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + ha_messagex(LOG_WARNING, "ldap: digest response contains invalid nonce"); } goto finally; } } - rec = get_cached_digest(ctx, nonce); /* Check to see if we're stale */ - if((expiry + ctx->cache_timeout) <= time(NULL)) + if((expiry + ctx->opts->cache_timeout) <= time(NULL)) { + ha_messagex(LOG_INFO, "ldap: nonce expired, sending stale challenge: %s", + dg.username); + stale = 1; goto finally; } if(!rec) { + ha_messagex(LOG_INFO, "ldap: no record in cache, creating one: %s", dg.username); + /* * If we're valid but don't have a record in the * cache then complete the record properly. @@ -1070,7 +1101,8 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, /* Increment our nonce count */ rec->nc++; - ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); + ret = digest_check(ctx->opts->realm, method, + ctx->opts->digest_ignoreuri ? NULL : uri, buf, &dg, rec); if(ret == HA_BADREQ) { @@ -1084,8 +1116,12 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, resp->detail = dg.username; /* Figure out if we need a new nonce */ - if((expiry + (ctx->cache_timeout - (ctx->cache_timeout / 8))) < time(NULL)) + if((expiry + (ctx->opts->cache_timeout - + (ctx->opts->cache_timeout / 8))) < time(NULL)) { + ha_messagex(LOG_INFO, "ldap: nonce almost expired, creating new one: %s", + dg.username); + digest_makenonce(nonce, g_ldap_secret, NULL); stale = 1; } @@ -1100,6 +1136,8 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, if(t[0]) ha_addheader(resp, "Authentication-Info", t); + ha_messagex(LOG_NOTICE, "ldap: validated digest user: %s", dg.username); + /* Put the connection into the cache */ if((r = save_cached_digest(ctx, rec)) < 0) ret = r; @@ -1179,18 +1217,6 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return HA_OK; } - else if(strcmp(name, "realm") == 0) - { - ctx->realm = value; - return HA_OK; - } - - else if(strcmp(name, "digestdomains") == 0) - { - ctx->domains = value; - return HA_OK; - } - else if(strcmp(name, "ldapscope") == 0) { if(strcmp(value, "sub") == 0 || strcmp(value, "subtree") == 0) @@ -1224,14 +1250,6 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout)); } -#ifdef _DEBUG - else if(strcmp(name, "digestdebugnonce") == 0) - { - ctx->debug_nonce = value; - return HA_OK; - } -#endif - return HA_FALSE; } @@ -1240,6 +1258,7 @@ int ldap_inithand(ha_context_t* context) /* Global initialization */ if(!context) { + ha_messagex(LOG_DEBUG, "ldap: generating secret"); return ha_genrandom(g_ldap_secret, DIGEST_SECRET_LEN); } @@ -1252,9 +1271,9 @@ int ldap_inithand(ha_context_t* context) ASSERT(ctx); /* Make sure there are some types of authentication we can do */ - if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) + if(!(context->opts.types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) { - ha_messagex(LOG_ERR, "LDAP module configured, but does not implement any " + ha_messagex(LOG_ERR, "ldap: module configured, but does not implement any " "configured authentication type."); return HA_FAILED; } @@ -1262,14 +1281,14 @@ int ldap_inithand(ha_context_t* context) /* Check for mandatory configuration */ if(!ctx->servers) { - ha_messagex(LOG_ERR, "LDAP configuration incomplete. " + ha_messagex(LOG_ERR, "ldap: configuration incomplete. " "Must have LDAPServers."); return HA_FAILED; } if(!ctx->dnmap && (!ctx->filter || !ctx->base)) { - ha_messagex(LOG_ERR, "LDAP configuration incomplete. " + ha_messagex(LOG_ERR, "ldap: configuration incomplete. " "When not using LDAPDNMap must specify LDAPBase and LDAPFilter."); return HA_FAILED; } @@ -1299,8 +1318,9 @@ int ldap_inithand(ha_context_t* context) memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max); /* Copy some settings over for easy access */ - ctx->cache_max = context->cache_max; - ctx->cache_timeout = context->cache_timeout; + ctx->opts = &(context->opts); + + ha_messagex(LOG_INFO, "ldap: initialized handler"); } return HA_OK; @@ -1333,6 +1353,8 @@ void ldap_destroy(ha_context_t* context) /* And free the connection pool */ free(ctx->pool); } + + ha_messagex(LOG_INFO, "ldap: uninitialized handler"); } int ldap_process(ha_context_t* context, ha_request_t* req, @@ -1341,7 +1363,7 @@ int ldap_process(ha_context_t* context, ha_request_t* req, ldap_context_t* ctx = (ldap_context_t*)context->data; time_t t = time(NULL); const char* header = NULL; - int ret; + int ret, r; ASSERT(req && resp && buf); ASSERT(req->args[AUTH_ARG_METHOD]); @@ -1350,21 +1372,24 @@ int ldap_process(ha_context_t* context, ha_request_t* req, ha_lock(NULL); /* Purge out stale connection stuff. */ - hash_purge(ctx->cache, t - ctx->cache_timeout); + r = hash_purge(ctx->cache, t - ctx->opts->cache_timeout); ha_unlock(NULL); + if(r > 0) + ha_messagex(LOG_DEBUG, "ldap: purged cache records: %d", r); /* We use this below to detect whether to send a default response */ resp->code = -1; /* Check the headers and see if we got a response thingy */ - if(context->types & HA_TYPE_DIGEST) + if(context->opts.types & HA_TYPE_DIGEST) { header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); if(header) { + ha_messagex(LOG_DEBUG, "ldap: processing basic auth header"); ret = digest_ldap_response(ctx, header, req->args[AUTH_ARG_METHOD], req->args[AUTH_ARG_URI], resp, buf); if(ret < 0) @@ -1373,11 +1398,12 @@ int ldap_process(ha_context_t* context, ha_request_t* req, } /* Or a basic authentication */ - if(!header && context->types & HA_TYPE_BASIC) + if(!header && context->opts.types & HA_TYPE_BASIC) { header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); if(header) { + ha_messagex(LOG_DEBUG, "ldap: processing digest auth header"); ret = basic_ldap_response(ctx, header, resp, buf); if(ret < 0) return ret; @@ -1390,21 +1416,22 @@ int ldap_process(ha_context_t* context, ha_request_t* req, { resp->code = HA_SERVER_DECLINE; - if(context->types & HA_TYPE_DIGEST) - { - ret = digest_ldap_challenge(ctx, resp, buf, 0); - if(ret < 0) - return ret; - } - - if(context->types & HA_TYPE_BASIC) + if(context->opts.types & HA_TYPE_BASIC) { - ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + ha_bufmcat(buf, "BASIC realm=\"", ctx->opts->realm , "\"", NULL); if(ha_buferr(buf)) return HA_CRITERROR; ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + ha_messagex(LOG_DEBUG, "ldap: sent basic auth request"); + } + + if(context->opts.types & HA_TYPE_DIGEST) + { + ret = digest_ldap_challenge(ctx, resp, buf, 0); + if(ret < 0) + return ret; } } |