/* * Reference: * http://docs.linux.cz/glibc-manual/libc_28.html#SEC603 * * Hit the memcache daemon that opens a socket to the memcache daemon, * does a request, and if not falls through then updates the memcache * with the corresponding response from the other NSS modules. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nss_memcache.h" #include "marshal.h" static __thread memcache_state_t *mc = NULL; static char path[] = "/tmp/socket"; // later at_fork() test the locks // Lock used around vars DEFINE_LOCK(backend); /* This module is call 'static' */ //enum nss_status _nss_memcache_setpwent (void); //enum nss_status _nss_memcache_endpwent (void); //enum nss_status _nss_memcache_getpwent_r (struct passwd *result, char *buffer, size_t buflen, struct passwd **pwbufp); /* struct passwd *_nss_memcache_getpwnam( const char *name ); */ struct passwd *_nss_memcache_getpwuid( uid_t uid ); enum nss_status _nss_memcache_getpwnam_r( const char *name, struct passwd *result, char *buffer, size_t buflen, struct passwd **pwbufp ); enum nss_status _nss_memcache_getpwuid_r( uid_t uid, struct passwd *result, char *buffer, size_t buflen, struct passwd **pwbufp ); /* * enum nss_status _nss_memcache_gethostbyname_r (PARAMS, STRUCTURE *result, char *buffer, size_t buflen); */ /* for thread safe-ish get*ent */ static __thread unsigned int last_pw_read = 0; //enum nss_status _nss_memcache_setpwent( void ) { // last_pw_read = 0; // return( NSS_STATUS_SUCCESS ); //} //enum nss_status _nss_memcache_endpwent( void ) { // return( NSS_STATUS_SUCCESS ); //} /* Always return NSS_STATUS_NOTFOUND since there is only a last entry * ie: no need for state */ #if 0 enum nss_status _nss_memcache_getpwent_r( struct passwd *result, char *buffer, size_t buflen, struct passwd **pwbufp ) { size_t line_len = 0; char *curbuf = buffer; /** DO NULL PTR CHECKS */ if( state == READ || state == CLOSE ) { return( NSS_STATUS_NOTFOUND ); } syslog( LOG_AUTHPRIV|LOG_DEBUG, "nss_memcache[%d]: entered getpwent_r successfully\n", getpid() ); /* check if the total data passwd entry line islonger than * buffer */ line_len = strlen( pw_name ) + 1; line_len += strlen( pw_passwd ) + 1; line_len += strlen( pw_gecos ) + 1; line_len += strlen( pw_dir ) + 1; line_len += strlen( pw_shell ) + 1; line_len += ( MAX_ID_LEN * 2 ) + 2; if( line_len > buflen ) { syslog( LOG_AUTHPRIV|LOG_DEBUG, "nss_memcache[%d]: buffer too small\n", getpid() ); //*errnop = errno = ERANGE; return( NSS_STATUS_TRYAGAIN ); } /* Populate the buffer and the struct * Not sure if this is required but I believe this is how * memory cleanup is made semi-safe * -wad */ result->pw_name = curbuf; curbuf += sprintf( curbuf, "%s", pw_name ); (curbuf++)[0] = '\0'; result->pw_passwd = curbuf; curbuf += sprintf( curbuf, "%s", pw_passwd ); (curbuf++)[0] = '\0'; result->pw_uid = pw_uid; result->pw_gid = pw_gid; curbuf += sprintf( curbuf, "%d:%d:", pw_uid, pw_gid ); result->pw_gecos = curbuf; curbuf += sprintf( curbuf, "%s", pw_gecos ); (curbuf++)[0] = '\0'; result->pw_dir = curbuf; curbuf += sprintf( curbuf, "%s", pw_dir ); (curbuf++)[0] = '\0'; result->pw_shell = curbuf; curbuf += sprintf( curbuf, "%s", pw_shell ); curbuf[0] = '\0'; /** [todo] should be checking the retvals **/ state = READ; /* Done and done */ syslog( LOG_AUTHPRIV|LOG_DEBUG, "nss_memcache[%d]: exitted getpwent_r successfully\n", getpid() ); return( NSS_STATUS_SUCCESS ); } #endif struct passwd *NSMC(getpwnam)(const char *name) { static struct passwd result; static char buffer[1024]; struct passwd *ptr = &result; DENTER; CHECK_NULL(name, NULL); _nss_memcache_getpwnam_r(name, &result, buffer, sizeof(buffer), &ptr); dreturn(&result); } #if 0 struct passwd *NSMC(getpwuid)(uid_t uid) { } #endif void NSMC(check_and_reconnect_socket)() { DENTER; if (mc == NULL) dreturn(); if (memcache_connected(mc)) dreturn(); /* reconnect */ memcache_reconnect(mc); DEXIT; } enum return_t NSMC(setup_socket)() { memcache_return_t ret; DENTER; // CONNECT TO SOCKET // BUNDLE THIS INTO A DIFF RE-CaLLABLE FUNCTION //memcache_connect(mc, "/var/run/memcache.sock"); ret = memcache_connect(mc, path); if (ret != MEMCACHE_SUCCESS) dreturn(RETURN_FAILURE); dreturn(RETURN_SUCCESS); } void NSMC(backend_add)(enum match_t type, char *key, char *data, size_t len) { int ret = 0; DENTER; NSMC(initialize_connection)(); LOCK(backend); // TODO: use type to determine timeout // mc_add dows NULL check for me // ret = memcache_add(mc, key, // key data, // data to store time(NULL)+300, // expiration date 0, //flags); len); // data size UNLOCK(backend); NSMC(destroy_connection)(); if (ret == RETURN_FAILURE) DLOG("failed to add key: %s", key); DEXIT; } enum return_t NSMC(fallthrough_successful)(enum nss_status status, char **ptr) { DENTER; if (status != 0) dreturn(RETURN_FAILURE); if (ptr == NULL) dreturn(RETURN_FAILURE); if (*ptr == NULL) dreturn(RETURN_FAILURE); dreturn(RETURN_SUCCESS); } enum return_t NSMC(check_maximum_keysize)(const char *fn, const char *key) { DENTER; if (strlen(key) > (MAX_KEY_SIZE - strlen(fn))) { DLOG("(%s) key too long. not caching.", fn); dreturn(RETURN_FAILURE); } dreturn(RETURN_SUCCESS); } void NSMC(create_backend_key)(char *dst, const char *prefix, const char *key) { DENTER; snprintf(dst, MAX_KEY_SIZE, "%s%s", prefix, key); DEXIT; } // I wish... //template(passwd, "lookup_template.c"); #define TMPL_TYPE passwd #include "lookup_template.c" #undef TMPL_TYPE enum nss_status NSMC(getpwnam_r)(const char *name, struct passwd *pwbuf, char *buffer, size_t buflen, struct passwd **pwbufp) { char key[MAX_KEY_SIZE]; enum nss_status status; enum return_t lookup_result = RETURN_SUCCESS; static __thread int fallthrough = 0; /* Include a lock for fallthrough in case of user-level threading. */ DEFINE_LOCK(fallthrough); DENTER; /* Perform NULL test */ CHECK_NULL(name, NSS_STATUS_NOTFOUND); CHECK_NULL(pwbuf, NSS_STATUS_NOTFOUND); CHECK_NULL(pwbufp, NSS_STATUS_NOTFOUND); if (buflen != 0) CHECK_NULL(buffer, NSS_STATUS_NOTFOUND); TEST_FOR(fallthrough); if (NSMC(check_maximum_keysize)(__func__, name) == RETURN_FAILURE) { DEXIT; dreturn(NSS_STATUS_UNAVAIL); } NSMC(create_backend_key)(key, __func__, name); // response is intrinsically trusted - pointer offsets and all. Must ensure // no tainting. lookup_result = NSMC(lookup_passwd)(key, strlen(key), pwbuf, buffer, buflen); switch (lookup_result) { case RETURN_SKIP: /* negative cache */ dreturn(NSS_STATUS_NOTFOUND); case RETURN_SUCCESS: *pwbufp = pwbuf; NSMC(unmarshal_str)(buffer, 5, /* # of elements below */ &pwbuf->pw_name, &pwbuf->pw_passwd, &pwbuf->pw_gecos, &pwbuf->pw_dir, &pwbuf->pw_shell); NSMC(destroy_connection)(); dreturn(NSS_STATUS_SUCCESS); default: DLOG("no backend match. falling through"); } FLIP(fallthrough); status = getpwnam_r(name, pwbuf, buffer, buflen, pwbufp); FLIP(fallthrough); if (NSMC(fallthrough_successful)(status, (char **)pwbufp) && !strcmp(pwbuf->pw_name, name)) { void *dupbuf = malloc(sizeof(struct passwd) + buflen); struct passwd *dupres = (struct passwd *)dupbuf; size_t buffer_used = 0; CHECK_NULL(dupbuf, NSS_STATUS_SUCCESS); DLOG("successful response from nss: %d -> %s", pwbuf->pw_uid, pwbuf->pw_name); DLOG("copying result struct to duplicate buffer"); memcpy(dupbuf, pwbuf, sizeof(struct passwd)); buffer_used = sizeof(struct passwd); DLOG("copying result char buffer to duplicate buffer"); memcpy(dupbuf + sizeof(struct passwd), buffer, buflen); DLOG("marshalling pointers in duplicated buffer"); buffer_used += NSMC(marshal_str)(buffer, 5, /* # of elements below */ &dupres->pw_name, &dupres->pw_passwd, &dupres->pw_gecos, &dupres->pw_dir, &dupres->pw_shell); DLOG("resulting amount of dupe buffer_used: %u", buffer_used); // TODO - add type after timeout is configurable NSMC(backend_add)(pw_pos, key, dupbuf, buffer_used); free(dupbuf); dreturn(NSS_STATUS_SUCCESS); } DLOG("no fallthrough match -- negative caching"); NSMC(backend_add)(pw_neg, key, NULL, 0); dreturn(NSS_STATUS_NOTFOUND); } enum return_t NSMC(destroy_connection)(void) { DENTER; LOCK(backend); if (mc == NULL) { UNLOCK(backend); dreturn(RETURN_SUCCESS); } memcache_destroy(mc); mc = NULL; UNLOCK(backend); dreturn(RETURN_SUCCESS); } enum return_t NSMC(initialize_connection)(void) { DENTER; LOCK(backend); // TODO TODO // THIS IS BAD - DO NOT LEAVE IT LIKE THIS AS IT // WILL BREAK CODE. // Add and then remove when the connection is shutdown. // Have it auto try to reconnect once. // May need to pathcv up libmemcache to avoid Aborts if (mc == NULL) { DLOG("connecting to mc server - %s", path); mc = memcache_initialize(); if (mc == NULL) { DLOG("failed to allocate backend instance"); UNLOCK(backend); dreturn(RETURN_FAILURE); } if (NSMC(setup_socket)() == RETURN_FAILURE) { UNLOCK(backend); dreturn(RETURN_FAILURE); } } NSMC(check_and_reconnect_socket)(); // TODO: add reconnect code here or patch libmemcache UNLOCK(backend); dreturn(RETURN_SUCCESS); } #if 0 enum nss_status _nss_memcache_getpwuid_r( uid_t uid, struct passwd *result, char *buffer, size_t buflen, struct passwd **pwbufp ) { /** DO NULL PTR CHECKS */ enum nss_status res; DEFINE_LOCK; #ifdef DEBUG syslog( LOG_AUTHPRIV|LOG_DEBUG, "nss_memcache[%d]: entered getpwuid_r\n", getpid() ); #endif // DO NOT USE THESE FOR REALZ. Make helpers to avoid double locking, // etc res = _nss_memcache_getpwnam_r( pw_name, result, buffer, buflen, pwbufp ); /* Done and done */ #ifdef DEBUG syslog( LOG_AUTHPRIV|LOG_DEBUG, "nss_memcache[%d]: exitting getpwuid_r\n", getpid() ); if (res) { syslog( LOG_AUTHPRIV|LOG_DEBUG, "nss_memcache[%d]: exitting getpwuid_r\n", getpid() ); } #endif return( res ); } #endif