#include "config.h"
#include <pwd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "list.h"
#include "common.h"
#include "auth-libsecret.h"
#include "auth.h"
struct authitem{
char *name; // item name
time_t touch_time; // item touch time
struct authinfo *info;
struct{
int child_cnt; // number of subitem
int max_child_cnt; // maximum number of subitem
struct authitem **childs; // sorted list of subitems
};
};
static char auth_login[64] = "guest";
static char *auth_fake_password = "********";
static struct authinfo authinfo_default = {{NULL, NULL}, 1, "", auth_login, ""};
static LIST authinfo_list = STATIC_LIST_INITIALIZER(authinfo_list);
static struct authitem authroot = {NULL, (time_t) 0, NULL, {0, 0, NULL}};
static pthread_mutex_t m_auth = PTHREAD_MUTEX_INITIALIZER;
void auth_set_default_login_name(const char *name){
strncpy(auth_login, name, sizeof(auth_login));
auth_login[sizeof(auth_login) - 1] = '\0';
DPRINTF(5, "login=%s\n", auth_login);
}
static struct authinfo * authinfo_create_new(
const char *domain,
const char *user,
const char *password){
size_t len;
struct authinfo *info;
len = sizeof(struct authinfo) +
strlen(domain) + strlen(user) + strlen(password) + 3;
if ((info = malloc(len)) == NULL) return NULL;
memset(info, 0, len);
info->domain = (char *) (info + 1);
info->user = info->domain + strlen(domain) + 1;
info->password = info->user + strlen(user) + 1;
strcpy(info->domain, domain);
strcpy(info->user, user);
strcpy(info->password, password);
return info;
}
static inline void authinfo_delete(struct authinfo *info){
free(info);
}
static inline int authinfo_compare(struct authinfo *info,
const char *domain,
const char *user,
const char *password){
return ((strcmp(info->domain, domain) == 0) &&
(strcmp(info->user, user) == 0) &&
(strcmp(info->password, password) == 0));
}
static struct authinfo * authinfo_find_in_list(
const char *domain,
const char *user,
const char *password){
struct authinfo *info;
LIST *elem;
elem = first_list_elem(&authinfo_list);
while(is_valid_list_elem(&authinfo_list, elem)){
info = list_entry(elem, struct authinfo, entries);
if (authinfo_compare(info, domain, user, password)) return info;
elem = elem->next;
}
return NULL;
}
static struct authinfo * authinfo_store_list(
const char *domain,
const char *user,
const char *password){
struct authinfo *info;
DPRINTF(10, "domain=%s, user=%s, password=%s\n",
domain, user, auth_fake_password);
info = authinfo_find_in_list(domain, user, password);
if (info != NULL){
remove_from_list(&authinfo_list, &info->entries);
}else{
info = authinfo_create_new(domain, user, password);
if (info == NULL) return NULL;
}
info->ref_count++;
add_to_list_back(&authinfo_list, &info->entries);
return info;
}
static void authinfo_release(struct authinfo *info){
info->ref_count--;
if (info->ref_count == 0){
remove_from_list(&authinfo_list, &info->entries);
authinfo_delete(info);
}
}
static struct authitem* authitem_create_item(const char *name){
struct authitem *item;
item = malloc(sizeof(struct authitem) + strlen(name) + 1);
if (item == NULL) return NULL;
memset(item, 0 , sizeof(struct authitem));
item->name = (char *) (item + 1);
strcpy(item->name, name);
item->touch_time = time(NULL);
return item;
}
static inline void authitem_delete_item(struct authitem *item){
if (item->info != NULL) authinfo_release(item->info);
if (item->childs != NULL) free(item->childs);
free(item);
}
static void authitem_delete_obsolete_items(struct authitem *item, time_t threshold){
int i;
for(i = item->child_cnt - 1; i >= 0; i--){
authitem_delete_obsolete_items(item->childs[i], threshold);
if ((item->childs[i]->info == NULL) &&
(item->childs[i]->childs == NULL)){
authitem_delete_item(item->childs[i]);
if (i != item->child_cnt - 1){
memmove(&item->childs[i],
&item->childs[i + 1],
(item->child_cnt - i - 1) * sizeof(struct authitem *));
}
item->child_cnt--;
}
}
if ((item->touch_time < threshold) && (item->info != NULL)){
authinfo_release(item->info);
item->info = NULL;
}
if ((item->child_cnt == 0) && (item->childs != NULL)){
free(item->childs);
item->childs = NULL;
item->max_child_cnt = 0;
}
}
/*
* This function search for an item in specified authitem subitems,
* subitems MUST be sorted alphabetically, so we can bisect.
*
* return:
* a) item position, if item with specified name was found
* b) negative value -(pos+1), if item was not found,
* where 'pos' is the position to insert the new element
*/
static int authitem_find_subitem(struct authitem *item, const char *name){
int first = 0, last = item->child_cnt - 1;
while(first <= last){
int i = (first + last) >> 1;
int result = strcasecmp(item->childs[i]->name, name);
if (result == 0) return i;
if (result < 0) first = i + 1;
else last = i - 1;
}
return -(first + 1);
}
/*
* This function insert an element to specified authitem,
* insert position MUST be obtained with authitem_find_subitem().
*
* return:
* a) zero, if no errors
* b) (-1), if insertion failed
*/
static int authitem_insert_subitem(struct authitem *item,
struct authitem *subitem, int pos){
if ((pos > item->child_cnt) || (pos < 0)) return -1;
if (item->max_child_cnt == item->child_cnt){
struct authitem **new_childs;
int new_max_cnt;
new_max_cnt = (item->max_child_cnt == 0) ?
64 : 2 * item->max_child_cnt;
new_childs = realloc(item->childs,
new_max_cnt * sizeof(struct authitem *));
if (new_childs == NULL) return -1;
item->max_child_cnt = new_max_cnt;
item->childs = new_childs;
}
if (pos < item->child_cnt){
memmove(&item->childs[pos + 1],
&item->childs[pos],
(item->child_cnt - pos) * sizeof(struct authitem *));
}
item->childs[pos] = subitem;
item->child_cnt++;
return 0;
}
static struct authitem * authitem_get_subitem(
struct authitem *item,
const char *name){
int pos;
struct authitem *subitem;
pos = authitem_find_subitem(item, name);
if (pos < 0){
/* create new subitem and add it to item */
subitem = authitem_create_item(name);
if (subitem == NULL) return NULL;
pos = -(pos + 1);
if (authitem_insert_subitem(item, subitem, pos) != 0){
authitem_delete_item(subitem);
return NULL;
}
}
return item->childs[pos];
}
struct authinfo * auth_get_authinfo(
const char *domain,
const char *server,
const char *share,
int *suitability){
int pos;
struct authitem *item;
struct authinfo *info;
DPRINTF(10, "domain=%s, server=%s, share=%s\n", domain, server, share);
if ((server == NULL) || (*server == '\0')) return NULL;
if (domain == NULL) domain = "";
if (share == NULL) share = "";
item = &authroot;
info = &authinfo_default;
*suitability = AUTH_FALLBACK;
pthread_mutex_lock(&m_auth);
if (item->info != NULL){
info = item->info;
*suitability = AUTH_MATCH_DEFAULT;
}
if (*domain != '\0'){
pos = authitem_find_subitem(item, domain);
if ((pos >= 0) && (item->childs[pos]->info != NULL)){
info = item->childs[pos]->info;
*suitability = AUTH_MATCH_DOMAIN_COMPAT;
}
}
if ((pos = authitem_find_subitem(item, server)) < 0) goto end;
item = item->childs[pos];
if (item->info != NULL){
info = item->info;
*suitability = AUTH_MATCH_SERVER;
}
if (*share == '\0') goto end;
if ((pos = authitem_find_subitem(item, share)) < 0) goto end;
item = item->childs[pos];
if (item->info != NULL){
info = item->info;
*suitability = AUTH_MATCH_RESOURCE;
}
end:
info->ref_count++;
pthread_mutex_unlock(&m_auth);
DPRINTF(10, "domain=%s, user=%s, password=%s, suitability=%d\n",
info->domain, info->user, auth_fake_password, *suitability);
return info;
}
void auth_release_authinfo(struct authinfo *info){
pthread_mutex_lock(&m_auth);
authinfo_release(info);
pthread_mutex_unlock(&m_auth);
}
int auth_store_auth_data(const char *server,
const char *share,
const char *domain,
const char *user,
const char *password){
int result;
struct authinfo *info;
struct authitem *item;
DPRINTF(10, "smb://%s/%s, domain=%s, user=%s, password=%s\n",
server, share, domain, user, auth_fake_password);
if ((user == NULL) || (*user == '\0')) return -1;
if (server == NULL) server = "";
if (share == NULL) share = "";
if (domain == NULL) domain = "";
if (password == NULL) password = "";
if ((*server == '\0') && (*share != '\0')) return -1;
result = -1;
item = &authroot;
pthread_mutex_lock(&m_auth);
if (*server == '\0') goto update_info;
if ((item = authitem_get_subitem(item, server)) == NULL) goto error;
if (*share == '\0') goto update_info;
if ((item = authitem_get_subitem(item, share)) == NULL) goto error;
update_info:
if ((item->info == NULL) ||
! authinfo_compare(item->info, domain, user, password)){
info = authinfo_store_list(domain, user, password);
if (info == NULL) goto error;
if (item->info != NULL) authinfo_release(item->info);
item->info = info;
}
item->touch_time = time(NULL);
result = 0;
error:
pthread_mutex_unlock(&m_auth);
return result;
}
void auth_delete_obsolete(time_t threshold){
pthread_mutex_lock(&m_auth);
authitem_delete_obsolete_items(&authroot, threshold);
pthread_mutex_unlock(&m_auth);
}