/* $Id: database.c,v 1.12 2006/07/17 13:30:00 bbs Exp $ */ #include "database.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int db_SHMID = 0; static SHM_t *db_SHM = NULL; static int db_SHMrefcount = 0; struct thread_data { char title[TTLEN]; int count; int last_post; }; struct hash_table { unsigned size; unsigned count; struct thread_data *table; }; static SHM_t * NewShareMemory() { if (db_SHMID == 0 || db_SHM == NULL) { db_SHMID = shmget(SHM_KEY, sizeof(SHM_t), 0); if (db_SHMID < 0) { fprintf(stderr, "[shmget error] key = %x\nerrno = %d: %s\n", SHM_KEY, errno, strerror(errno)); return NULL; } else { db_SHM = shmat(db_SHMID, NULL, 0); if (db_SHM == (void *)-1) { fprintf(stderr, "[shmget error] key = %x\nerrno = %d: %s\n", SHM_KEY, errno, strerror(errno)); return NULL; } } if (!db_SHM->loaded) { shmdt(db_SHM); fprintf(stderr, "shared memory not loaded\n"); return NULL; } } return db_SHM; } /* Initialize to use the database. Return 0 if success to initialize. */ int db_Database_Init(db_database_t *db) { db->SHM = NewShareMemory(); if (db->SHM == NULL) return 1; ++db_SHMrefcount; db->userinfo = NULL; return 0; } /* Initialize the database with attached shared memory. Return 0 for success initialized. */ int db_Database_InitFromExist(db_database_t *db, SHM_t *shm) { if (!shm->loaded) { fprintf(stderr, "shared memory not loaded\n"); return 1; } db->SHM = shm; db->userinfo = NULL; return 0; } /* Finish using the database. This function should be called before exit. */ void db_Database_Done(db_database_t *db) { --db_SHMrefcount; if (db_SHMrefcount <= 0) shmdt(db_SHM); } /* Calculate a hash value for search users. Source: StringHash() in mbbsd */ static unsigned StringHash(const char *id) { int i, hash; hash = 0; for (i=0; id[i]!='\0'; ++i) { hash = (hash << 8) | (hash >> 24); hash ^= (unsigned char)toupper(id[i]); } return (hash * 2654435769UL) >> (32 - HASH_BITS); } char * genpasswd(char *pw) { if (pw[0]) { char saltc[2], c; int i; i = 9 * getpid(); saltc[0] = i & 077; saltc[1] = (i >> 6) & 077; for (i = 0; i < 2; i++) { c = saltc[i] + '.'; if (c > '9') c += 7; if (c > 'Z') c += 6; saltc[i] = c; } return crypt(pw, saltc); } return ""; } void remove_from_uhash(db_database_t *db, int n) { /* * note: after remove_from_uhash(), you should add_to_uhash() (likely with a * different name) */ int h = StringHash(db->SHM->userid[n]); int *p = &(db->SHM->hash_head[h]); int times; for (times = 0; times < MAX_USERS && (*p != -1 && *p != n); ++times) p = &(db->SHM->next_in_hash[*p]); // if (times == MAX_USERS) // abort_bbs(0); if (*p == n) *p = db->SHM->next_in_hash[n]; } void add_to_uhash(db_database_t *db, int n, char *id) { int *p, h = StringHash(id); int times; strlcpy(db->SHM->userid[n], id, sizeof(db->SHM->userid[n])); p = &(db->SHM->hash_head[h]); for (times = 0; times < MAX_USERS && *p != -1; ++times) p = &(db->SHM->next_in_hash[*p]); // if (times == MAX_USERS) // return; db->SHM->next_in_hash[*p = n] = -1; } int db_Database_Register(db_database_t *db, const char *id, const char *passwd, const char *fromhost) { int uid; int i, hash; int fd; char passbuf[STRLEN]; const char *pw; userec_t userec; userec_t newuser; strcpy(passbuf, "testing"); /* search user id from share memory */ /* Source: login_query(), initcuser(), searchuser(), passwd_query() */ if (id[0] == '\0') return 0; uid = db->SHM->number; i = 0; while (i < uid) { if (!db->SHM->userid[i++][0]) break; } uid = i + 1; /* write user record to file */ /* Source: passwd_update() */ newuser.userlevel = PERM_DEFAULT; strcpy(newuser.username, "測試者"); newuser.numlogins = 0; newuser.uflag = COLOR_FLAG | BRDSORT_FLAG | MOVIE_FLAG; newuser.firstlogin = newuser.lastlogin = 0; newuser.money = 0; newuser.pager = 1; strcpy(newuser.userid, id); passbuf[8] = '\0'; strncpy(newuser.passwd, genpasswd(passbuf), PASSLEN); if (uid < 1 || uid > MAX_USERS) return 0; newuser.money = 0; if ((fd = open(FN_PASSWD, O_WRONLY)) < 0) return 0; lseek(fd, sizeof(userec_t) * (uid), SEEK_SET); write(fd, &newuser, sizeof(userec_t)); close(fd); if (uid > db->SHM->number) db->SHM->number = uid; else remove_from_uhash(db, uid - 1); add_to_uhash(db, uid - 1, id); strcpy(db->SHM->userid[uid], id); return 1; } int db_Database_FindUser(db_database_t *db, const char *id) { int uid, i; /* search user id from share memory */ /* Source: login_query(), initcuser(), searchuser(), passwd_query() */ if (id==NULL || id[0] == '\0') return -1; uid = db->SHM->hash_head[StringHash(id)]; i = 0; for (;;) { if (i >= MAX_USERS || uid < 0 || uid > MAX_USERS) return -1; if (!strcasecmp(db->SHM->userid[uid], id)) break; uid = db->SHM->next_in_hash[uid]; ++i; } return uid; } int db_Database_ReadUserRecord(int uid, userec_t *userec) { int fd; /* read user record from file */ /* Source: passwd_query() */ if (uid < 0 || uid > MAX_USERS) return 1; if ((fd = open(FN_PASSWD, O_RDONLY)) < 0) return 1; if (lseek(fd, uid * sizeof(userec_t), SEEK_SET) == -1) { close(fd); return 1; } if (read(fd, userec, sizeof *userec) != sizeof *userec) { close(fd); return 1; } close(fd); return 0; } int db_Database_Login(db_database_t *db, const char *id, const char *passwd, const char *fromhost) { int uid; int i, hash; const char *pw; userec_t userec; userinfo_t ui; uid = db_Database_FindUser(db, id); if (uid < 0) return 1; if (db_Database_ReadUserRecord(uid, &userec) != 0) return 4; /* check password */ if (strcmp(id, STR_GUEST)) { /* if not guest */ pw = crypt(passwd, userec.passwd); if(pw == NULL || strcmp(pw, userec.passwd) != 0) return 2; } /* setup userinfo */ /* Source: setup_utmp() */ memset(&ui, 0, sizeof ui); ui.pid = getpid(); ui.fromip = inet_addr(fromhost); ui.uid = uid; ui.mode = LOGIN; /* Note: mailalert is disabled here, until load_mailalert() is completed uinfo.mailalert = load_mailalert(cuser->userid); */ ui.mailalert = 0; #ifdef ASSESS ui.goodpost = userec.goodpost; ui.badpost = userec.badpost; ui.goodsale = userec.goodsale; ui.badsale = userec.badsale; #endif ui.random = rand(); ui.userlevel = userec.userlevel; ui.sex = userec.sex % 8; ui.lastact = time(NULL); strlcpy(ui.userid, userec.userid, sizeof(ui.userid)); strcpy(ui.userid, id); strlcpy(ui.username, userec.username, sizeof(ui.username)); if (!userec.fake_ip) strlcpy(ui.from, fromhost, sizeof ui.from); else strlcpy(ui.from, inet_ntoa((*(struct in_addr *)(&userec.fake_ip))), sizeof ui.from); ui.five_win = userec.five_win; ui.five_lose = userec.five_lose; ui.five_tie = userec.five_tie; ui.chc_win = userec.chc_win; ui.chc_lose = userec.chc_lose; ui.chc_tie = userec.chc_tie; ui.pager = userec.pager % 5; memcpy(ui.mind, userec.mind, 4); #ifdef WHERE /* ui.from_alias = where(fromhost); */ ui.from_alias = 0; #endif #ifndef FAST_LOGIN /* setuserfile(buf, "remoteuser"); strlcpy(remotebuf, fromhost, sizeof(fromhost)); strcat(remotebuf, ctime(&now)); remotebuf[strlen(remotebuf) - 1] = 0; add_distinct(buf, remotebuf); */ #endif /* find an empty utmp entry */ /* Source: getnewutmpent() in mbbsd */ i = 0; hash = StringHash(id) % USHM_SIZE; for (;;) { if (i >= USHM_SIZE) return 3; if (hash >= USHM_SIZE) hash = 0; db->userinfo = db->SHM->uinfo + hash; if (!(db->userinfo->pid)) { memcpy(db->userinfo, &ui, sizeof ui); break; } ++i; ++hash; } db->userinfo->active = 1; db->SHM->UTMPneedsort = 1; return 0; } int db_Database_LoginSession(db_database_t *db, int session) { if (session < 0 || session > USHM_SIZE) return 1; if (db->SHM->uinfo[session].pid <= 0) return 2; db->userinfo = db->SHM->uinfo + session; return 0; } int db_Database_Logout(db_database_t *db) { if (db->userinfo == NULL) { /* it has not logged in */ return 0; } db->userinfo->pid = 0; db->userinfo->active = 0; db->userinfo = NULL; return 0; } /* The session number returned from this function can be used to login using db_Database_LoginSession(). Return negative value if error. */ int db_Database_Session(db_database_t *db) { if (db->userinfo == NULL) return -1; return db->userinfo - db->SHM->uinfo; } int db_Database_CurrentState(db_database_t *db) { if (db != NULL && db->userinfo != NULL) return db->userinfo->mode; else return 0; } void db_Database_SetCurrentState(db_database_t *db, int state) { if (db != NULL && db->userinfo != NULL) db->userinfo->mode = state; } int db_Database_ClearIdleTime(db_database_t *db) { if (db->userinfo == NULL) { return 1; } else { time(&db->userinfo->lastact); return 0; } } unsigned hash_function(const char title[TTLEN]) { unsigned hash = 0; unsigned i; for (i=0; title[i]; ++i) { hash = (hash<<5)+(hash<<2)+(hash>>1)+2654435761U; } return hash; } int thread_data_not_empty(struct thread_data *td) { return (td->title[0] != 0); } void initialize(struct hash_table *ht, unsigned init_size) { ht->table = calloc(init_size, sizeof *ht->table); // if (ht->table == NULL) ht->size = init_size; ht->count = 0; } /* 使用完 hash table 之後,釋放所佔用的記憶體空間 */ void finialize(struct hash_table *ht) { free(ht->table); } /* basic_add 由內部使用,不要直接呼叫 */ void basic_add(struct hash_table *ht, struct thread_data *td) { unsigned hash_value = hash_function(td->title) % ht->size; while (thread_data_not_empty( &(ht->table[hash_value]) )) { hash_value = (hash_value + 1) % ht->size; } ht->table[hash_value] = *td; ++ht->count; } /* grow 也是由內部使用 */ void grow(struct hash_table *ht) { struct hash_table new_table; unsigned i; initialize(&new_table, 16384); for (i=0; isize; ++i) if (thread_data_not_empty( &(ht->table[i]) )) basic_add(&new_table, &(ht->table[i]) ); finialize(ht); *ht = new_table; } /* 把新的資料加進 hash table 裡 */ void add(struct hash_table *ht, struct thread_data td) { while (ht->count > ht->size/2) grow(ht); basic_add(ht, &td); } /* 由 hash table 裡找尋資料 */ struct thread_data *find(struct hash_table *ht, const char title[TTLEN]) { unsigned i = hash_function(title) % ht->size; while (thread_data_not_empty( &(ht->table[i]) )) { if (!strcmp(title, ht->table[i].title)) return &ht->table[i]; /* 找到 */ } return NULL; /* 找不到 */ }