From e6fe8fcb688c89770c55bc7a81422c4141c44d42 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Sat, 7 Nov 2009 23:10:26 -0500 Subject: Add cleanup task --- server/Makefile.am | 2 + server/config/etc/sssd.api.d/sssd-ldap.conf | 1 + server/providers/ipa/ipa_common.c | 3 +- server/providers/ldap/ldap_common.c | 20 +- server/providers/ldap/ldap_common.h | 21 +- server/providers/ldap/ldap_id.c | 395 ++++++++++++++------ server/providers/ldap/ldap_id_cleanup.c | 539 ++++++++++++++++++++++++++++ server/providers/ldap/ldap_id_enum.c | 89 +++-- server/providers/ldap/sdap.h | 1 + 9 files changed, 915 insertions(+), 156 deletions(-) create mode 100644 server/providers/ldap/ldap_id_cleanup.c (limited to 'server') diff --git a/server/Makefile.am b/server/Makefile.am index bdc2f986..08c0295e 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -531,6 +531,7 @@ stress_tests_LDADD = \ libsss_ldap_la_SOURCES = \ providers/ldap/ldap_id.c \ providers/ldap/ldap_id_enum.c \ + providers/ldap/ldap_id_cleanup.c \ providers/ldap/ldap_auth.c \ providers/ldap/ldap_init.c \ providers/ldap/ldap_common.c \ @@ -580,6 +581,7 @@ libsss_ipa_la_SOURCES = \ providers/ipa/ipa_timerules.c \ providers/ldap/ldap_id.c \ providers/ldap/ldap_id_enum.c \ + providers/ldap/ldap_id_cleanup.c \ providers/ldap/ldap_auth.c \ providers/ldap/ldap_common.c \ providers/ldap/sdap_async.c \ diff --git a/server/config/etc/sssd.api.d/sssd-ldap.conf b/server/config/etc/sssd.api.d/sssd-ldap.conf index af1e66cf..34aaa65b 100644 --- a/server/config/etc/sssd.api.d/sssd-ldap.conf +++ b/server/config/etc/sssd.api.d/sssd-ldap.conf @@ -22,6 +22,7 @@ ldap_rootdse_last_usn = str, None [provider/ldap/id] ldap_search_timeout = int, None ldap_enumeration_refresh_timeout = int, None +ldap_purge_cache_timeout = int, None ldap_id_use_start_tls = bool, None, false ldap_user_search_base = str, None ldap_user_search_scope = str, None diff --git a/server/providers/ipa/ipa_common.c b/server/providers/ipa/ipa_common.c index a571aead..4d7dc952 100644 --- a/server/providers/ipa/ipa_common.c +++ b/server/providers/ipa/ipa_common.c @@ -51,6 +51,7 @@ struct dp_option ipa_def_ldap_opts[] = { { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER }, { "entry_cache_timeout", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER }, { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, @@ -211,7 +212,7 @@ done: /* the following preprocessor code is used to keep track of * the options in the ldap module, so that if they change and ipa * is not updated correspondingly this will trigger a build error */ -#if SDAP_OPTS_BASIC > 28 +#if SDAP_OPTS_BASIC > 29 #error There are ldap options not accounted for #endif diff --git a/server/providers/ldap/ldap_common.c b/server/providers/ldap/ldap_common.c index d8e73f71..deffb4aa 100644 --- a/server/providers/ldap/ldap_common.c +++ b/server/providers/ldap/ldap_common.c @@ -44,6 +44,7 @@ struct dp_option default_basic_opts[] = { { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER }, { "entry_cache_timoeut", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER }, { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, @@ -287,20 +288,23 @@ void sdap_mark_offline(struct sdap_id_ctx *ctx) int sdap_id_setup_tasks(struct sdap_id_ctx *ctx) { - struct tevent_timer *enum_task; + struct timeval tv; int ret = EOK; /* set up enumeration task */ if (ctx->be->domain->enumerate) { /* run the first one in a couple of seconds so that we have time to * finish initializations first*/ - ctx->last_run = tevent_timeval_current_ofs(2, 0); - enum_task = tevent_add_timer(ctx->be->ev, ctx, ctx->last_run, - ldap_id_enumerate, ctx); - if (!enum_task) { - DEBUG(0, ("FATAL: failed to setup enumeration task!\n")); - ret = EFAULT; - } + tv = tevent_timeval_current_ofs(2, 0); + ret = ldap_id_enumerate_set_timer(ctx, tv); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not runnig we need to schedule it */ + + /* run the first one in a couple of seconds so that we have time to + * finish initializations first*/ + tv = tevent_timeval_current_ofs(2, 0); + ret = ldap_id_cleanup_set_timer(ctx, tv); } return ret; diff --git a/server/providers/ldap/ldap_common.h b/server/providers/ldap/ldap_common.h index 0c51428d..9cc30f84 100644 --- a/server/providers/ldap/ldap_common.h +++ b/server/providers/ldap/ldap_common.h @@ -69,11 +69,26 @@ int ldap_get_options(TALLOC_CTX *memctx, const char *conf_path, struct sdap_options **_opts); -void ldap_id_enumerate(struct tevent_context *ev, - struct tevent_timer *tt, - struct timeval tv, void *pvt); +int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv); +int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv); bool sdap_connected(struct sdap_id_ctx *ctx); void sdap_mark_offline(struct sdap_id_ctx *ctx); +struct tevent_req *users_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type); +int users_get_recv(struct tevent_req *req); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type); +int groups_get_recv(struct tevent_req *req); + #endif /* _LDAP_COMMON_H_ */ diff --git a/server/providers/ldap/ldap_id.c b/server/providers/ldap/ldap_id.c index 3268d734..124ee7d8 100644 --- a/server/providers/ldap/ldap_id.c +++ b/server/providers/ldap/ldap_id.c @@ -36,20 +36,26 @@ struct users_get_state { struct tevent_context *ev; struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int filter_type; char *filter; const char **attrs; }; static void users_get_connect_done(struct tevent_req *subreq); -static void users_get_op_done(struct tevent_req *subreq); - -static struct tevent_req *users_get_send(TALLOC_CTX *memctx, - struct tevent_context *ev, - struct sdap_id_ctx *ctx, - const char *name, - int filter_type, - int attrs_type) +static void users_get_done(struct tevent_req *subreq); +static void users_get_delete(struct tevent_req *subreq); + +struct tevent_req *users_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type) { struct tevent_req *req, *subreq; struct users_get_state *state; @@ -61,8 +67,12 @@ static struct tevent_req *users_get_send(TALLOC_CTX *memctx, state->ev = ev; state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = state->ctx->be->domain; + state->name = name; + state->filter_type = filter_type; - switch(filter_type) { + switch (filter_type) { case BE_FILTER_NAME: attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name; break; @@ -106,15 +116,14 @@ static struct tevent_req *users_get_send(TALLOC_CTX *memctx, } subreq = sdap_get_users_send(state, state->ev, - state->ctx->be->domain, - state->ctx->be->sysdb, + state->domain, state->sysdb, state->ctx->opts, state->ctx->gsh, state->attrs, state->filter); if (!subreq) { ret = ENOMEM; goto fail; } - tevent_req_set_callback(subreq, users_get_op_done, req); + tevent_req_set_callback(subreq, users_get_done, req); return req; @@ -144,83 +153,130 @@ static void users_get_connect_done(struct tevent_req *subreq) } subreq = sdap_get_users_send(state, state->ev, - state->ctx->be->domain, - state->ctx->be->sysdb, + state->domain, state->sysdb, state->ctx->opts, state->ctx->gsh, state->attrs, state->filter); if (!subreq) { tevent_req_error(req, ENOMEM); return; } - tevent_req_set_callback(subreq, users_get_op_done, req); + tevent_req_set_callback(subreq, users_get_done, req); } -static void users_get_op_done(struct tevent_req *subreq) +static void users_get_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + char *endptr; + uid_t uid; int ret; ret = sdap_get_users_recv(subreq, NULL, NULL); talloc_zfree(subreq); - if (ret) { + if (ret && ret != ENOENT) { tevent_req_error(req, ret); return; } + if (ret == ENOENT) { + if (strchr(state->name, '*')) { + /* it was an enumeration */ + tevent_req_error(req, ret); + return; + } + + switch (state->filter_type) { + case BE_FILTER_NAME: + subreq = sysdb_delete_user_send(state, state->ev, + state->sysdb, NULL, + state->domain, state->name, 0); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, users_get_delete, req); + return; + + case BE_FILTER_IDNUM: + errno = 0; + uid = (uid_t)strtol(state->name, &endptr, 0); + if (errno || *endptr || (state->name == endptr)) { + tevent_req_error(req, errno); + return; + } + + subreq = sysdb_delete_user_send(state, state->ev, + state->sysdb, NULL, + state->domain, NULL, uid); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, users_get_delete, req); + return; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + tevent_req_done(req); } -static void users_get_done(struct tevent_req *req) +static void users_get_delete(struct tevent_req *subreq) { - struct be_req *breq = tevent_req_callback_data(req, struct be_req); - struct sdap_id_ctx *ctx; - enum tevent_req_state tstate; - uint64_t err = EIO; - int dp_err = DP_ERR_OK; - const char *error = NULL; - int ret = EOK; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + int ret; - if (tevent_req_is_error(req, &tstate, &err)) { - ret = err; + ret = sysdb_delete_user_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("User (%s) delete returned %d (%s)\n", + state->name, ret, strerror(ret))); } - if (ret) { - dp_err = DP_ERR_FATAL; - error = "Enum Users Failed"; + tevent_req_done(req); +} - if (ret == ETIMEDOUT) { - dp_err = DP_ERR_TIMEOUT; - } - if (ret == ETIMEDOUT || ret == EFAULT) { - ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, - struct sdap_id_ctx); - sdap_mark_offline(ctx); - } - } +int users_get_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); - sdap_handler_done(breq, dp_err, ret, error); + return EOK; } + /* =Groups-Related-Functions-(by-name,by-uid)============================= */ struct groups_get_state { struct tevent_context *ev; struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int filter_type; char *filter; const char **attrs; }; static void groups_get_connect_done(struct tevent_req *subreq); -static void groups_get_op_done(struct tevent_req *subreq); - -static struct tevent_req *groups_get_send(TALLOC_CTX *memctx, - struct tevent_context *ev, - struct sdap_id_ctx *ctx, - const char *name, - int filter_type, - int attrs_type) +static void groups_get_done(struct tevent_req *subreq); +static void groups_get_delete(struct tevent_req *subreq); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type) { struct tevent_req *req, *subreq; struct groups_get_state *state; @@ -232,6 +288,10 @@ static struct tevent_req *groups_get_send(TALLOC_CTX *memctx, state->ev = ev; state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = state->ctx->be->domain; + state->name = name; + state->filter_type = filter_type; switch(filter_type) { case BE_FILTER_NAME: @@ -277,15 +337,14 @@ static struct tevent_req *groups_get_send(TALLOC_CTX *memctx, } subreq = sdap_get_groups_send(state, state->ev, - state->ctx->be->domain, - state->ctx->be->sysdb, - state->ctx->opts, state->ctx->gsh, - state->attrs, state->filter); + state->domain, state->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); if (!subreq) { ret = ENOMEM; goto fail; } - tevent_req_set_callback(subreq, groups_get_op_done, req); + tevent_req_set_callback(subreq, groups_get_done, req); return req; @@ -315,64 +374,105 @@ static void groups_get_connect_done(struct tevent_req *subreq) } subreq = sdap_get_groups_send(state, state->ev, - state->ctx->be->domain, - state->ctx->be->sysdb, + state->domain, state->sysdb, state->ctx->opts, state->ctx->gsh, state->attrs, state->filter); if (!subreq) { tevent_req_error(req, ENOMEM); return; } - tevent_req_set_callback(subreq, groups_get_op_done, req); + tevent_req_set_callback(subreq, groups_get_done, req); } -static void groups_get_op_done(struct tevent_req *subreq) +static void groups_get_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + char *endptr; + gid_t gid; int ret; ret = sdap_get_groups_recv(subreq, NULL, NULL); talloc_zfree(subreq); - if (ret) { + if (ret && ret != ENOENT) { tevent_req_error(req, ret); return; } + if (ret == ENOENT) { + if (strchr(state->name, '*')) { + /* it was an enumeration */ + tevent_req_error(req, ret); + return; + } + + switch (state->filter_type) { + case BE_FILTER_NAME: + subreq = sysdb_delete_group_send(state, state->ev, + state->sysdb, NULL, + state->domain, state->name, 0); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_delete, req); + return; + + case BE_FILTER_IDNUM: + errno = 0; + gid = (gid_t)strtol(state->name, &endptr, 0); + if (errno || *endptr || (state->name == endptr)) { + tevent_req_error(req, errno); + return; + } + + subreq = sysdb_delete_group_send(state, state->ev, + state->sysdb, NULL, + state->domain, NULL, gid); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_delete, req); + return; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + tevent_req_done(req); } -static void groups_get_done(struct tevent_req *req) +static void groups_get_delete(struct tevent_req *subreq) { - struct be_req *breq = tevent_req_callback_data(req, struct be_req); - struct sdap_id_ctx *ctx; - enum tevent_req_state tstate; - uint64_t err = EIO; - int dp_err = DP_ERR_OK; - const char *error = NULL; - int ret = EOK; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + int ret; - if (tevent_req_is_error(req, &tstate, &err)) { - ret = err; + ret = sysdb_delete_group_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Group (%s) delete returned %d (%s)\n", + state->name, ret, strerror(ret))); } - if (ret) { - dp_err = DP_ERR_FATAL; - error = "Enum Groups Failed"; + tevent_req_done(req); +} - if (ret == ETIMEDOUT) { - dp_err = DP_ERR_TIMEOUT; - } - if (ret == ETIMEDOUT || ret == EFAULT) { - ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, - struct sdap_id_ctx); - sdap_mark_offline(ctx); - } - } +int groups_get_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); - return sdap_handler_done(breq, dp_err, ret, error); + return EOK; } + /* =Get-Groups-for-User================================================== */ struct groups_by_user_state { @@ -383,7 +483,7 @@ struct groups_by_user_state { }; static void groups_by_user_connect_done(struct tevent_req *subreq); -static void groups_by_user_op_done(struct tevent_req *subreq); +static void groups_by_user_done(struct tevent_req *subreq); static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, struct tevent_context *ev, @@ -431,7 +531,7 @@ static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, ret = ENOMEM; goto fail; } - tevent_req_set_callback(subreq, groups_by_user_op_done, req); + tevent_req_set_callback(subreq, groups_by_user_done, req); return req; @@ -469,10 +569,10 @@ static void groups_by_user_connect_done(struct tevent_req *subreq) tevent_req_error(req, ENOMEM); return; } - tevent_req_set_callback(subreq, groups_by_user_op_done, req); + tevent_req_set_callback(subreq, groups_by_user_done, req); } -static void groups_by_user_op_done(struct tevent_req *subreq) +static void groups_by_user_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); @@ -488,35 +588,11 @@ static void groups_by_user_op_done(struct tevent_req *subreq) tevent_req_done(req); } -static void groups_by_user_done(struct tevent_req *req) +int groups_by_user_recv(struct tevent_req *req) { - struct be_req *breq = tevent_req_callback_data(req, struct be_req); - struct sdap_id_ctx *ctx; - enum tevent_req_state tstate; - uint64_t err = EIO; - int dp_err = DP_ERR_OK; - const char *error = NULL; - int ret = EOK; - - if (tevent_req_is_error(req, &tstate, &err)) { - ret = err; - } - - if (ret) { - dp_err = DP_ERR_FATAL; - error = "Init Groups Failed"; - - if (ret == ETIMEDOUT) { - dp_err = DP_ERR_TIMEOUT; - } - if (ret == ETIMEDOUT || ret == EFAULT) { - ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, - struct sdap_id_ctx); - sdap_mark_offline(ctx); - } - } + TEVENT_REQ_RETURN_ON_ERROR(req); - return sdap_handler_done(breq, dp_err, ret, error); + return EOK; } @@ -525,6 +601,11 @@ static void groups_by_user_done(struct tevent_req *req) /* FIXME: embed this function in sssd_be and only call out * specific functions from modules ? */ + +static void sdap_account_info_users_done(struct tevent_req *req); +static void sdap_account_info_groups_done(struct tevent_req *req); +static void sdap_account_info_initgr_done(struct tevent_req *req); + void sdap_account_info_handler(struct be_req *breq) { struct sdap_id_ctx *ctx; @@ -557,7 +638,7 @@ void sdap_account_info_handler(struct be_req *breq) return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory"); } - tevent_req_set_callback(req, users_get_done, breq); + tevent_req_set_callback(req, sdap_account_info_users_done, breq); break; @@ -576,7 +657,7 @@ void sdap_account_info_handler(struct be_req *breq) return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory"); } - tevent_req_set_callback(req, groups_get_done, breq); + tevent_req_set_callback(req, sdap_account_info_groups_done, breq); break; @@ -601,7 +682,7 @@ void sdap_account_info_handler(struct be_req *breq) if (!req) ret = ENOMEM; /* tevent_req_set_callback(req, groups_by_user_done, breq); */ - tevent_req_set_callback(req, groups_by_user_done, breq); + tevent_req_set_callback(req, sdap_account_info_initgr_done, breq); break; @@ -613,3 +694,87 @@ void sdap_account_info_handler(struct be_req *breq) if (ret != EOK) return sdap_handler_done(breq, DP_ERR_FATAL, ret, err); } +static void sdap_account_info_users_done(struct tevent_req *req) +{ + struct be_req *breq = tevent_req_callback_data(req, struct be_req); + struct sdap_id_ctx *ctx; + int dp_err = DP_ERR_OK; + const char *error = NULL; + int ret; + + ret = users_get_recv(req); + talloc_zfree(req); + + if (ret) { + dp_err = DP_ERR_FATAL; + error = "Enum Users Failed"; + + if (ret == ETIMEDOUT) { + dp_err = DP_ERR_TIMEOUT; + } + if (ret == ETIMEDOUT || ret == EFAULT) { + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + sdap_mark_offline(ctx); + } + } + + sdap_handler_done(breq, dp_err, ret, error); +} + +static void sdap_account_info_groups_done(struct tevent_req *req) +{ + struct be_req *breq = tevent_req_callback_data(req, struct be_req); + struct sdap_id_ctx *ctx; + int dp_err = DP_ERR_OK; + const char *error = NULL; + int ret; + + ret = groups_get_recv(req); + talloc_zfree(req); + + if (ret) { + dp_err = DP_ERR_FATAL; + error = "Enum Groups Failed"; + + if (ret == ETIMEDOUT) { + dp_err = DP_ERR_TIMEOUT; + } + if (ret == ETIMEDOUT || ret == EFAULT) { + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + sdap_mark_offline(ctx); + } + } + + return sdap_handler_done(breq, dp_err, ret, error); +} + +static void sdap_account_info_initgr_done(struct tevent_req *req) +{ + struct be_req *breq = tevent_req_callback_data(req, struct be_req); + struct sdap_id_ctx *ctx; + int dp_err = DP_ERR_OK; + const char *error = NULL; + int ret; + + ret = groups_by_user_recv(req); + talloc_zfree(req); + + if (ret) { + dp_err = DP_ERR_FATAL; + error = "Init Groups Failed"; + + if (ret == ETIMEDOUT) { + dp_err = DP_ERR_TIMEOUT; + } + if (ret == ETIMEDOUT || ret == EFAULT) { + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + sdap_mark_offline(ctx); + } + } + + return sdap_handler_done(breq, dp_err, ret, error); +} + diff --git a/server/providers/ldap/ldap_id_cleanup.c b/server/providers/ldap/ldap_id_cleanup.c new file mode 100644 index 00000000..7267b354 --- /dev/null +++ b/server/providers/ldap/ldap_id_cleanup.c @@ -0,0 +1,539 @@ +/* + SSSD + + LDAP Identity Cleanup Functions + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +/* ==Cleanup-Task========================================================= */ + +struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); +static void ldap_id_cleanup_reschedule(struct tevent_req *req); + +static void ldap_id_cleanup_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static void ldap_id_cleanup_timer(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, void *pvt) +{ + struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx); + struct tevent_timer *timeout; + struct tevent_req *req; + int delay; + + if (be_is_offline(ctx->be)) { + DEBUG(4, ("Backend is marked offline, retry later!\n")); + /* schedule starting from now, not the last run */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_cleanup_set_timer(ctx, tv); + return; + } + + ctx->last_run = tv; + + req = ldap_id_cleanup_send(ctx, ev, ctx); + if (!req) { + DEBUG(1, ("Failed to schedule cleanup, retrying later!\n")); + /* schedule starting from now, not the last run */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_cleanup_set_timer(ctx, tv); + return; + } + tevent_req_set_callback(req, ldap_id_cleanup_reschedule, ctx); + + /* if cleanup takes so long, either we try to cleanup too + * frequently, or something went seriously wrong */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + timeout = tevent_add_timer(ctx->be->ev, req, tv, + ldap_id_cleanup_timeout, req); + return; +} + +static void ldap_id_cleanup_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_id_ctx *ctx = tevent_req_callback_data(req, + struct sdap_id_ctx); + int delay; + + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + DEBUG(1, ("Cleanup timed out! Timeout too small? (%ds)!\n", delay)); + + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); + + talloc_zfree(req); +} + +static void ldap_id_cleanup_reschedule(struct tevent_req *req) +{ + struct sdap_id_ctx *ctx = tevent_req_callback_data(req, + struct sdap_id_ctx); + enum tevent_req_state tstate; + uint64_t err; + struct timeval tv; + int delay; + + if (tevent_req_is_error(req, &tstate, &err)) { + /* On error schedule starting from now, not the last run */ + tv = tevent_timeval_current(); + } else { + tv = ctx->last_run; + } + talloc_zfree(req); + + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_add(&tv, delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); +} + + + +int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv) +{ + struct tevent_timer *cleanup_task; + + DEBUG(6, ("Scheduling next cleanup at %ld.%ld\n", + (long)tv.tv_sec, (long)tv.tv_usec)); + + cleanup_task = tevent_add_timer(ctx->be->ev, ctx, + tv, ldap_id_cleanup_timer, ctx); + if (!cleanup_task) { + DEBUG(0, ("FATAL: failed to setup cleanup task!\n")); + return EFAULT; + } + + return EOK; +} + + + +struct global_cleanup_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; +}; + +static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); +static void ldap_id_cleanup_users_done(struct tevent_req *subreq); +static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); +static void ldap_id_cleanup_groups_done(struct tevent_req *subreq); + +struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct global_cleanup_state *state; + struct tevent_req *req, *subreq; + + req = tevent_req_create(memctx, &state, struct global_cleanup_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + + subreq = cleanup_users_send(state, ev, ctx); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req); + + return req; +} + +static void ldap_id_cleanup_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct global_cleanup_state *state = tevent_req_data(req, + struct global_cleanup_state); + enum tevent_req_state tstate; + uint64_t err = 0; + + if (tevent_req_is_error(subreq, &tstate, &err)) { + if (tstate != TEVENT_REQ_USER_ERROR) { + err = EIO; + } + if (err != ENOENT) { + goto fail; + } + } + talloc_zfree(subreq); + + subreq = cleanup_groups_send(state, state->ev, state->ctx); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req); + + return; + +fail: + if (err) { + DEBUG(9, ("User cleanup failed with: (%d)[%s]\n", + (int)err, strerror(err))); + + sdap_mark_offline(state->ctx); + } + + DEBUG(1, ("Failed to cleanup users, retrying later!\n")); + tevent_req_done(req); +} + +static void ldap_id_cleanup_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct global_cleanup_state *state = tevent_req_data(req, + struct global_cleanup_state); + enum tevent_req_state tstate; + uint64_t err; + + if (tevent_req_is_error(subreq, &tstate, &err)) { + if (tstate != TEVENT_REQ_USER_ERROR) { + err = EIO; + } + if (err != ENOENT) { + goto fail; + } + } + talloc_zfree(subreq); + + tevent_req_done(req); + return; + +fail: + /* always go offline on failures */ + sdap_mark_offline(state->ctx); + DEBUG(1, ("Failed to cleanup groups (%d [%s]), retrying later!\n", + (int)err, strerror(err))); + tevent_req_done(req); +} + + +/* ==User-Cleanup-Process================================================= */ + +struct cleanup_users_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + struct sysdb_handle *handle; + + struct ldb_message **msgs; + size_t count; + int cur; +}; + +static void cleanup_users_process(struct tevent_req *subreq); +static void cleanup_users_update(struct tevent_req *req); +static void cleanup_users_up_done(struct tevent_req *subreq); + +static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct tevent_req *req, *subreq; + struct cleanup_users_state *state; + static const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + + req = tevent_req_create(memctx, &state, struct cleanup_users_state); + if (!req) { + return NULL; + } + + state->ev = ev; + state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = ctx->be->domain; + state->msgs = NULL; + state->count = 0; + state->cur = 0; + + subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, (long)now); + if (!subfilter) { + DEBUG(2, ("Failed to build filter\n")); + talloc_zfree(req); + return NULL; + } + + subreq = sysdb_search_users_send(state, state->ev, + state->sysdb, NULL, + state->domain, subfilter, attrs); + if (!subreq) { + DEBUG(2, ("Failed to send entry search\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, cleanup_users_process, req); + + return req; +} + +static void cleanup_users_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_users_state *state = tevent_req_data(req, + struct cleanup_users_state); + int ret; + + ret = sysdb_search_users_recv(subreq, state, &state->count, &state->msgs); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOENT) { + tevent_req_done(req); + return; + } + tevent_req_error(req, ret); + return; + } + + DEBUG(4, ("Found %d expired user entries!\n", state->count)); + + if (state->count == 0) { + tevent_req_done(req); + } + + cleanup_users_update(req); +} + +static void cleanup_users_update(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct cleanup_users_state *state = tevent_req_data(req, + struct cleanup_users_state); + const char *str; + + str = ldb_msg_find_attr_as_string(state->msgs[state->cur], + SYSDB_NAME, NULL); + if (!str) { + DEBUG(2, ("Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(state->msgs[state->cur]->dn))); + tevent_req_error(req, EFAULT); + return; + } + + subreq = users_get_send(state, state->ev, state->ctx, + str, BE_FILTER_NAME, BE_ATTR_CORE); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, cleanup_users_up_done, req); +} + +static void cleanup_users_up_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_users_state *state = tevent_req_data(req, + struct cleanup_users_state); + int ret; + + ret = users_get_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("User check returned: %d(%s)\n", + ret, strerror(ret))); + } + + /* if the entry doesn't need to be purged, remove it from the list */ + if (ret != ENOENT) { + talloc_zfree(state->msgs[state->cur]); + } + + state->cur++; + if (state->cur < state->count) { + cleanup_users_update(req); + return; + } + + tevent_req_done(req); +} + +/* ==Group-Cleanup-Process================================================ */ + +struct cleanup_groups_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + struct sysdb_handle *handle; + + struct ldb_message **msgs; + size_t count; + int cur; +}; + +static void cleanup_groups_process(struct tevent_req *subreq); +static void cleanup_groups_update(struct tevent_req *req); +static void cleanup_groups_up_done(struct tevent_req *subreq); + +static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct tevent_req *req, *subreq; + struct cleanup_groups_state *state; + static const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + + req = tevent_req_create(memctx, &state, struct cleanup_groups_state); + if (!req) { + return NULL; + } + + state->ev = ev; + state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = ctx->be->domain; + state->msgs = NULL; + state->count = 0; + state->cur = 0; + + subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, (long)now); + if (!subfilter) { + DEBUG(2, ("Failed to build filter\n")); + talloc_zfree(req); + return NULL; + } + + subreq = sysdb_search_groups_send(state, state->ev, + state->sysdb, NULL, + state->domain, subfilter, attrs); + if (!subreq) { + DEBUG(2, ("Failed to send entry search\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, cleanup_groups_process, req); + + return req; +} + +static void cleanup_groups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_groups_state *state = tevent_req_data(req, + struct cleanup_groups_state); + int ret; + + ret = sysdb_search_groups_recv(subreq, state, &state->count, &state->msgs); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOENT) { + tevent_req_done(req); + return; + } + tevent_req_error(req, ret); + return; + } + + DEBUG(4, ("Found %d expired group entries!\n", state->count)); + + if (state->count == 0) { + tevent_req_done(req); + } + + cleanup_groups_update(req); +} + +static void cleanup_groups_update(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct cleanup_groups_state *state = tevent_req_data(req, + struct cleanup_groups_state); + const char *str; + + str = ldb_msg_find_attr_as_string(state->msgs[state->cur], + SYSDB_NAME, NULL); + if (!str) { + DEBUG(2, ("Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(state->msgs[state->cur]->dn))); + tevent_req_error(req, EFAULT); + return; + } + + subreq = groups_get_send(state, state->ev, state->ctx, + str, BE_FILTER_NAME, BE_ATTR_CORE); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, cleanup_groups_up_done, req); +} + +static void cleanup_groups_up_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_groups_state *state = tevent_req_data(req, + struct cleanup_groups_state); + int ret; + + ret = groups_get_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("User check returned: %d(%s)\n", + ret, strerror(ret))); + } + + state->cur++; + if (state->cur < state->count) { + cleanup_groups_update(req); + return; + } + + tevent_req_done(req); +} + diff --git a/server/providers/ldap/ldap_id_enum.c b/server/providers/ldap/ldap_id_enum.c index b7035efe..10b8d96c 100644 --- a/server/providers/ldap/ldap_id_enum.c +++ b/server/providers/ldap/ldap_id_enum.c @@ -31,31 +31,36 @@ #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap_async.h" +extern struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); + /* ==Enumeration-Task===================================================== */ static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev, struct sdap_id_ctx *ctx); + static void ldap_id_enumerate_reschedule(struct tevent_req *req); -static void ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, - struct timeval tv); static void ldap_id_enumerate_timeout(struct tevent_context *ev, struct tevent_timer *te, struct timeval tv, void *pvt); -void ldap_id_enumerate(struct tevent_context *ev, - struct tevent_timer *tt, - struct timeval tv, void *pvt) +static void ldap_id_enumerate_timer(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, void *pvt) { struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx); struct tevent_timer *timeout; struct tevent_req *req; - int ert; + int delay; if (be_is_offline(ctx->be)) { DEBUG(4, ("Backend is marked offline, retry later!\n")); /* schedule starting from now, not the last run */ - ldap_id_enumerate_set_timer(ctx, tevent_timeval_current()); + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); return; } @@ -65,16 +70,17 @@ void ldap_id_enumerate(struct tevent_context *ev, if (!req) { DEBUG(1, ("Failed to schedule enumeration, retrying later!\n")); /* schedule starting from now, not the last run */ - ldap_id_enumerate_set_timer(ctx, tevent_timeval_current()); + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); return; } tevent_req_set_callback(req, ldap_id_enumerate_reschedule, ctx); /* if enumeration takes so long, either we try to enumerate too * frequently, or something went seriously wrong */ - tv = tevent_timeval_current(); - ert = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); - tv = tevent_timeval_add(&tv, ert, 0); + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); timeout = tevent_add_timer(ctx->be->ev, req, tv, ldap_id_enumerate_timeout, req); return; @@ -87,11 +93,13 @@ static void ldap_id_enumerate_timeout(struct tevent_context *ev, struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); struct sdap_id_ctx *ctx = tevent_req_callback_data(req, struct sdap_id_ctx); - int ert; + int delay; - ert = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); - DEBUG(1, ("Enumeration timed out! Timeout too small? (%ds)!\n", ert)); - ldap_id_enumerate_set_timer(ctx, tevent_timeval_current()); + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + DEBUG(1, ("Enumeration timed out! Timeout too small? (%ds)!\n", delay)); + + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); talloc_zfree(req); } @@ -100,9 +108,10 @@ static void ldap_id_enumerate_reschedule(struct tevent_req *req) { struct sdap_id_ctx *ctx = tevent_req_callback_data(req, struct sdap_id_ctx); - struct timeval tv; enum tevent_req_state tstate; uint64_t err; + struct timeval tv; + int delay; if (tevent_req_is_error(req, &tstate, &err)) { /* On error schedule starting from now, not the last run */ @@ -112,25 +121,29 @@ static void ldap_id_enumerate_reschedule(struct tevent_req *req) } talloc_zfree(req); - ldap_id_enumerate_set_timer(ctx, ctx->last_run); + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_add(&tv, delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); } -static void ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, - struct timeval tv) + + +int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv) { struct tevent_timer *enum_task; - int ert; - ert = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); - tv = tevent_timeval_add(&tv, ert, 0); - enum_task = tevent_add_timer(ctx->be->ev, ctx, tv, ldap_id_enumerate, ctx); + DEBUG(6, ("Scheduling next enumeration at %ld.%ld\n", + (long)tv.tv_sec, (long)tv.tv_usec)); + + enum_task = tevent_add_timer(ctx->be->ev, ctx, + tv, ldap_id_enumerate_timer, ctx); if (!enum_task) { DEBUG(0, ("FATAL: failed to setup enumeration task!\n")); - /* shutdown! */ - exit(1); + return EFAULT; } -} + return EOK; +} struct global_enum_state { @@ -146,6 +159,7 @@ static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_id_ctx *ctx); static void ldap_id_enum_groups_done(struct tevent_req *subreq); +static void ldap_id_enum_cleanup_done(struct tevent_req *subreq); static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev, struct sdap_id_ctx *ctx) @@ -182,7 +196,9 @@ static void ldap_id_enum_users_done(struct tevent_req *subreq) if (tstate != TEVENT_REQ_USER_ERROR) { err = EIO; } - goto fail; + if (err != ENOENT) { + goto fail; + } } talloc_zfree(subreq); @@ -219,11 +235,18 @@ static void ldap_id_enum_groups_done(struct tevent_req *subreq) if (tstate != TEVENT_REQ_USER_ERROR) { err = EIO; } - goto fail; + if (err != ENOENT) { + goto fail; + } } talloc_zfree(subreq); - tevent_req_done(req); + subreq = ldap_id_cleanup_send(state, state->ev, state->ctx); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ldap_id_enum_cleanup_done, req); + return; fail: @@ -234,6 +257,14 @@ fail: tevent_req_done(req); } +static void ldap_id_enum_cleanup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + talloc_zfree(subreq); + tevent_req_done(req); +} + /* ==User-Enumeration===================================================== */ diff --git a/server/providers/ldap/sdap.h b/server/providers/ldap/sdap.h index fec9eefa..8330bd6f 100644 --- a/server/providers/ldap/sdap.h +++ b/server/providers/ldap/sdap.h @@ -107,6 +107,7 @@ enum sdap_basic_opt { SDAP_OFFLINE_TIMEOUT, SDAP_FORCE_UPPER_CASE_REALM, SDAP_ENUM_REFRESH_TIMEOUT, + SDAP_CACHE_PURGE_TIMEOUT, SDAP_ENTRY_CACHE_TIMEOUT, SDAP_TLS_CACERT, SDAP_TLS_CACERTDIR, -- cgit