acct_plugin.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /******************************************************************************
  2. Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
  3. This program is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU General Public License
  5. version 2 as published by the Free Software Foundation.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program; if not, write to the Free Software
  12. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  13. Contributors:
  14. Hewlett-Packard Development Company, L.P.
  15. ******************************************************************************/
  16. /* Account Policy plugin */
  17. #include <stdio.h>
  18. #include <string.h>
  19. #include <ctype.h>
  20. #include <time.h>
  21. #include "slapi-plugin.h"
  22. #include "acctpolicy.h"
  23. /*
  24. * acct_policy_dn_is_config()
  25. *
  26. * Checks if dn is a plugin config entry.
  27. */
  28. static int
  29. acct_policy_dn_is_config(Slapi_DN *sdn)
  30. {
  31. int ret = 0;
  32. slapi_log_error(SLAPI_LOG_TRACE, PLUGIN_NAME,
  33. "--> automember_dn_is_config\n");
  34. if (sdn == NULL) {
  35. goto bail;
  36. }
  37. /* If an alternate config area is configured, treat it's child
  38. * entries as config entries. If the alternate config area is
  39. * not configured, treat children of the top-level plug-in
  40. * config entry as our config entries. */
  41. if (acct_policy_get_config_area()) {
  42. if (slapi_sdn_issuffix(sdn, acct_policy_get_config_area()) &&
  43. slapi_sdn_compare(sdn, acct_policy_get_config_area())) {
  44. ret = 1;
  45. }
  46. } else {
  47. if (slapi_sdn_issuffix(sdn, acct_policy_get_plugin_sdn()) &&
  48. slapi_sdn_compare(sdn, acct_policy_get_plugin_sdn())) {
  49. ret = 1;
  50. }
  51. }
  52. bail:
  53. slapi_log_error(SLAPI_LOG_TRACE, PLUGIN_NAME,
  54. "<-- automember_dn_is_config\n");
  55. return ret;
  56. }
  57. /*
  58. Checks bind entry for last login state and compares current time with last
  59. login time plus the limit to decide whether to deny the bind.
  60. */
  61. static int
  62. acct_inact_limit( Slapi_PBlock *pb, const char *dn, Slapi_Entry *target_entry, acctPolicy *policy )
  63. {
  64. char *lasttimestr = NULL;
  65. time_t lim_t, last_t, cur_t;
  66. int rc = 0; /* Optimistic default */
  67. acctPluginCfg *cfg;
  68. config_rd_lock();
  69. cfg = get_config();
  70. if( ( lasttimestr = get_attr_string_val( target_entry,
  71. cfg->state_attr_name ) ) != NULL ) {
  72. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  73. "\"%s\" login timestamp is %s\n", dn, lasttimestr );
  74. } else if( cfg->alt_state_attr_name && (( lasttimestr = get_attr_string_val( target_entry,
  75. cfg->alt_state_attr_name ) ) != NULL) ) {
  76. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  77. "\"%s\" alternate timestamp is %s\n", dn, lasttimestr );
  78. } else {
  79. /* the primary or alternate attribute might not yet exist eg.
  80. * if only lastlogintime is specified and it id the first login
  81. */
  82. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  83. "\"%s\" has no value for stateattr or altstateattr \n", dn );
  84. goto done;
  85. }
  86. last_t = gentimeToEpochtime( lasttimestr );
  87. cur_t = time( (time_t*)0 );
  88. lim_t = policy->inactivitylimit;
  89. /* Finally do the time comparison */
  90. if( cur_t > last_t + lim_t ) {
  91. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  92. "\"%s\" has exceeded inactivity limit (%ld > (%ld + %ld))\n",
  93. dn, cur_t, last_t, lim_t );
  94. rc = 1;
  95. goto done;
  96. } else {
  97. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  98. "\"%s\" is within inactivity limit (%ld < (%ld + %ld))\n",
  99. dn, cur_t, last_t, lim_t );
  100. }
  101. done:
  102. config_unlock();
  103. /* Deny bind; the account has exceeded the inactivity limit */
  104. if( rc == 1 ) {
  105. slapi_send_ldap_result( pb, LDAP_CONSTRAINT_VIOLATION, NULL,
  106. "Account inactivity limit exceeded."
  107. " Contact system administrator to reset.", 0, NULL );
  108. }
  109. slapi_ch_free_string( &lasttimestr );
  110. return( rc );
  111. }
  112. /*
  113. This is called after binds, it updates an attribute in the account
  114. with the current time.
  115. */
  116. static int
  117. acct_record_login( const char *dn )
  118. {
  119. int ldrc;
  120. int rc = 0; /* Optimistic default */
  121. LDAPMod *mods[2];
  122. LDAPMod mod;
  123. struct berval *vals[2];
  124. struct berval val;
  125. char *timestr = NULL;
  126. acctPluginCfg *cfg;
  127. void *plugin_id;
  128. Slapi_PBlock *modpb = NULL;
  129. int skip_mod_attrs = 1; /* value doesn't matter as long as not NULL */
  130. config_rd_lock();
  131. cfg = get_config();
  132. /* if we are not allowed to modify the state attr we're done
  133. * this could be intentional, so just return
  134. */
  135. if (! update_is_allowed_attr(cfg->always_record_login_attr) )
  136. goto done;
  137. plugin_id = get_identity();
  138. timestr = epochtimeToGentime( time( (time_t*)0 ) );
  139. val.bv_val = timestr;
  140. val.bv_len = strlen( val.bv_val );
  141. vals [0] = &val;
  142. vals [1] = NULL;
  143. mod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
  144. mod.mod_type = cfg->always_record_login_attr;
  145. mod.mod_bvalues = vals;
  146. mods[0] = &mod;
  147. mods[1] = NULL;
  148. modpb = slapi_pblock_new();
  149. slapi_modify_internal_set_pb( modpb, dn, mods, NULL, NULL,
  150. plugin_id, SLAPI_OP_FLAG_NO_ACCESS_CHECK |
  151. SLAPI_OP_FLAG_BYPASS_REFERRALS );
  152. slapi_pblock_set( modpb, SLAPI_SKIP_MODIFIED_ATTRS, &skip_mod_attrs );
  153. slapi_modify_internal_pb( modpb );
  154. slapi_pblock_get( modpb, SLAPI_PLUGIN_INTOP_RESULT, &ldrc );
  155. if (ldrc != LDAP_SUCCESS) {
  156. slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
  157. "Recording %s=%s failed on \"%s\" err=%d\n", cfg->always_record_login_attr,
  158. timestr, dn, ldrc );
  159. rc = -1;
  160. goto done;
  161. } else {
  162. slapi_log_error( SLAPI_LOG_PLUGIN, POST_PLUGIN_NAME,
  163. "Recorded %s=%s on \"%s\"\n", cfg->always_record_login_attr, timestr, dn );
  164. }
  165. done:
  166. config_unlock();
  167. slapi_pblock_destroy( modpb );
  168. slapi_ch_free_string( &timestr );
  169. return( rc );
  170. }
  171. /*
  172. Handles bind preop callbacks
  173. */
  174. int
  175. acct_bind_preop( Slapi_PBlock *pb )
  176. {
  177. const char *dn = NULL;
  178. Slapi_DN *sdn = NULL;
  179. Slapi_Entry *target_entry = NULL;
  180. int rc = 0; /* Optimistic default */
  181. int ldrc;
  182. acctPolicy *policy = NULL;
  183. void *plugin_id;
  184. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  185. "=> acct_bind_preop\n" );
  186. plugin_id = get_identity();
  187. /* This does not give a copy, so don't free it */
  188. if( slapi_pblock_get( pb, SLAPI_BIND_TARGET_SDN, &sdn ) != 0 ) {
  189. slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
  190. "Error retrieving target DN\n" );
  191. rc = -1;
  192. goto done;
  193. }
  194. dn = slapi_sdn_get_dn(sdn);
  195. /* The plugin wouldn't get called for anonymous binds but let's check */
  196. if ( dn == NULL ) {
  197. goto done;
  198. }
  199. ldrc = slapi_search_internal_get_entry( sdn, NULL, &target_entry,
  200. plugin_id );
  201. /* There was a problem retrieving the entry */
  202. if( ldrc != LDAP_SUCCESS ) {
  203. if( ldrc != LDAP_NO_SUCH_OBJECT ) {
  204. /* The problem is not a bad bind or virtual entry; halt bind */
  205. slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
  206. "Failed to retrieve entry \"%s\": %d\n", dn, ldrc );
  207. rc = -1;
  208. }
  209. goto done;
  210. }
  211. if( get_acctpolicy( pb, target_entry, plugin_id, &policy ) ) {
  212. slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
  213. "Account Policy object for \"%s\" is missing\n", dn );
  214. rc = -1;
  215. goto done;
  216. }
  217. /* Null policy means target isnt's under the influence of a policy */
  218. if( policy == NULL ) {
  219. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  220. "\"%s\" is not governed by an account policy\n", dn);
  221. goto done;
  222. }
  223. /* Check whether the account is in violation of inactivity limit */
  224. rc = acct_inact_limit( pb, dn, target_entry, policy );
  225. /* ...Any additional account policy enforcement goes here... */
  226. done:
  227. /* Internal error */
  228. if( rc == -1 ) {
  229. slapi_send_ldap_result( pb, LDAP_UNWILLING_TO_PERFORM, NULL, NULL, 0, NULL );
  230. }
  231. slapi_entry_free( target_entry );
  232. free_acctpolicy( &policy );
  233. slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  234. "<= acct_bind_preop\n" );
  235. return( rc == 0 ? CALLBACK_OK : CALLBACK_ERR );
  236. }
  237. /*
  238. This is called after binds, it updates an attribute in the entry that the
  239. bind DN corresponds to with the current time if it has an account policy
  240. specifier.
  241. */
  242. int
  243. acct_bind_postop( Slapi_PBlock *pb )
  244. {
  245. char *dn = NULL;
  246. int ldrc, tracklogin = 0;
  247. int rc = 0; /* Optimistic default */
  248. Slapi_DN *sdn = NULL;
  249. Slapi_Entry *target_entry = NULL;
  250. acctPluginCfg *cfg;
  251. void *plugin_id;
  252. slapi_log_error( SLAPI_LOG_PLUGIN, POST_PLUGIN_NAME,
  253. "=> acct_bind_postop\n" );
  254. plugin_id = get_identity();
  255. /* Retrieving SLAPI_CONN_DN from the pb gives a copy */
  256. if( slapi_pblock_get( pb, SLAPI_CONN_DN, &dn ) != 0 ) {
  257. slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
  258. "Error retrieving bind DN\n" );
  259. rc = -1;
  260. goto done;
  261. }
  262. /* Client is anonymously bound */
  263. if( dn == NULL ) {
  264. goto done;
  265. }
  266. config_rd_lock();
  267. cfg = get_config();
  268. tracklogin = cfg->always_record_login;
  269. /* We're not always tracking logins, so check whether the entry is
  270. covered by an account policy to decide whether we should track */
  271. if( tracklogin == 0 ) {
  272. sdn = slapi_sdn_new_normdn_byref( dn );
  273. ldrc = slapi_search_internal_get_entry( sdn, NULL, &target_entry,
  274. plugin_id );
  275. if( ldrc != LDAP_SUCCESS ) {
  276. slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
  277. "Failed to retrieve entry \"%s\": %d\n", dn, ldrc );
  278. rc = -1;
  279. goto done;
  280. } else {
  281. if( target_entry && has_attr( target_entry,
  282. cfg->spec_attr_name, NULL ) ) {
  283. tracklogin = 1;
  284. }
  285. }
  286. }
  287. config_unlock();
  288. if( tracklogin ) {
  289. rc = acct_record_login( dn );
  290. }
  291. /* ...Any additional account policy postops go here... */
  292. done:
  293. if( rc == -1 ) {
  294. slapi_send_ldap_result( pb, LDAP_UNWILLING_TO_PERFORM, NULL, NULL, 0, NULL );
  295. }
  296. slapi_entry_free( target_entry );
  297. slapi_sdn_free( &sdn );
  298. slapi_ch_free_string( &dn );
  299. slapi_log_error( SLAPI_LOG_PLUGIN, POST_PLUGIN_NAME,
  300. "<= acct_bind_postop\n" );
  301. return( rc == 0 ? CALLBACK_OK : CALLBACK_ERR );
  302. }
  303. static int acct_pre_op( Slapi_PBlock *pb, int modop )
  304. {
  305. Slapi_DN *sdn = 0;
  306. Slapi_Entry *e = 0;
  307. Slapi_Mods *smods = 0;
  308. LDAPMod **mods;
  309. int free_entry = 0;
  310. char *errstr = NULL;
  311. int ret = SLAPI_PLUGIN_SUCCESS;
  312. slapi_log_error(SLAPI_LOG_TRACE, PRE_PLUGIN_NAME, "--> acct_pre_op\n");
  313. slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
  314. if (acct_policy_dn_is_config(sdn)) {
  315. /* Validate config changes, but don't apply them.
  316. * This allows us to reject invalid config changes
  317. * here at the pre-op stage. Applying the config
  318. * needs to be done at the post-op stage. */
  319. if (LDAP_CHANGETYPE_ADD == modop) {
  320. slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
  321. /* If the entry doesn't exist, just bail and
  322. * let the server handle it. */
  323. if (e == NULL) {
  324. goto bail;
  325. }
  326. } else if (LDAP_CHANGETYPE_MODIFY == modop) {
  327. /* Fetch the entry being modified so we can
  328. * create the resulting entry for validation. */
  329. if (sdn) {
  330. slapi_search_internal_get_entry(sdn, 0, &e, get_identity());
  331. free_entry = 1;
  332. }
  333. /* If the entry doesn't exist, just bail and
  334. * let the server handle it. */
  335. if (e == NULL) {
  336. goto bail;
  337. }
  338. /* Grab the mods. */
  339. slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
  340. smods = slapi_mods_new();
  341. slapi_mods_init_byref(smods, mods);
  342. /* Apply the mods to create the resulting entry. */
  343. if (mods && (slapi_entry_apply_mods(e, mods) != LDAP_SUCCESS)) {
  344. /* The mods don't apply cleanly, so we just let this op go
  345. * to let the main server handle it. */
  346. goto bailmod;
  347. }
  348. } else if (modop == LDAP_CHANGETYPE_DELETE){
  349. ret = LDAP_UNWILLING_TO_PERFORM;
  350. slapi_log_error(SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
  351. "acct_pre_op: can not delete plugin config entry [%d]\n", ret);
  352. } else {
  353. errstr = slapi_ch_smprintf("acct_pre_op: invalid op type %d", modop);
  354. ret = LDAP_PARAM_ERROR;
  355. goto bail;
  356. }
  357. }
  358. bailmod:
  359. /* Clean up smods. */
  360. if (LDAP_CHANGETYPE_MODIFY == modop) {
  361. slapi_mods_free(&smods);
  362. }
  363. bail:
  364. if (free_entry && e)
  365. slapi_entry_free(e);
  366. if (ret) {
  367. slapi_log_error(SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
  368. "acct_pre_op: operation failure [%d]\n", ret);
  369. slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
  370. slapi_ch_free((void **)&errstr);
  371. slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ret);
  372. ret = SLAPI_PLUGIN_FAILURE;
  373. }
  374. slapi_log_error(SLAPI_LOG_TRACE, PRE_PLUGIN_NAME, "<-- acct_pre_op\n");
  375. return ret;
  376. }
  377. int
  378. acct_add_pre_op( Slapi_PBlock *pb )
  379. {
  380. return acct_pre_op(pb, LDAP_CHANGETYPE_ADD);
  381. }
  382. int
  383. acct_mod_pre_op( Slapi_PBlock *pb )
  384. {
  385. return acct_pre_op(pb, LDAP_CHANGETYPE_MODIFY);
  386. }
  387. int
  388. acct_del_pre_op( Slapi_PBlock *pb )
  389. {
  390. return acct_pre_op(pb, LDAP_CHANGETYPE_DELETE);
  391. }
  392. int
  393. acct_post_op(Slapi_PBlock *pb)
  394. {
  395. Slapi_DN *sdn = NULL;
  396. slapi_log_error(SLAPI_LOG_TRACE, POST_PLUGIN_NAME,
  397. "--> acct_policy_post_op\n");
  398. slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
  399. if (acct_policy_dn_is_config(sdn)){
  400. if( acct_policy_load_config_startup( pb, get_identity() ) ) {
  401. slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
  402. "acct_policy_start failed to load configuration\n" );
  403. return( CALLBACK_ERR );
  404. }
  405. }
  406. slapi_log_error(SLAPI_LOG_TRACE, POST_PLUGIN_NAME,
  407. "<-- acct_policy_mod_post_op\n");
  408. return SLAPI_PLUGIN_SUCCESS;
  409. }