diff --git a/include/article_op.h b/include/article_op.h index f9423df..c3d46e7 100644 --- a/include/article_op.h +++ b/include/article_op.h @@ -9,8 +9,10 @@ #ifndef _ARTICLE_OP_H_ #define _ARTICLE_OP_H_ +#include "section_list.h" #include extern int display_article_meta(int32_t aid); +extern int article_excerption_set(SECTION_LIST *p_section, int32_t aid, int8_t set); #endif //_ARTICLE_OP_H_ diff --git a/src/article_op.c b/src/article_op.c index 561856e..8a22197 100644 --- a/src/article_op.c +++ b/src/article_op.c @@ -12,8 +12,12 @@ #include "article_op.h" #include "bbs.h" +#include "database.h" #include "io.h" +#include "log.h" #include "screen.h" +#include "user_priv.h" +#include int display_article_meta(int32_t aid) { @@ -26,3 +30,241 @@ int display_article_meta(int32_t aid) return 0; } + +int article_excerption_set(SECTION_LIST *p_section, int32_t aid, int8_t set) +{ + MYSQL *db = NULL; + MYSQL_RES *rs = NULL; + MYSQL_ROW row; + char sql[SQL_BUFFER_LEN]; + int ret = 0; + int uid = 0; + int tid = 0; + int sid = 0; + int8_t transship = 0; + int8_t excerption = 0; + + if (p_section == NULL) + { + log_error("NULL pointer error"); + ret = -1; + goto cleanup; + } + + if (set) + { + set = 1; + } + + db = db_open(); + if (db == NULL) + { + log_error("db_open() error: %s", mysql_error(db)); + ret = -1; + goto cleanup; + } + + // Begin transaction + if (mysql_query(db, "SET autocommit=0") != 0) + { + log_error("SET autocommit=0 error: %s", mysql_error(db)); + ret = -1; + goto cleanup; + } + + if (mysql_query(db, "BEGIN") != 0) + { + log_error("Begin transaction error: %s", mysql_error(db)); + ret = -1; + goto cleanup; + } + + snprintf(sql, sizeof(sql), + "SELECT UID, TID, SID, transship, excerption FROM bbs " + "WHERE AID = %d AND visible FOR UPDATE", + aid); + if (mysql_query(db, sql) != 0) + { + log_error("Query article error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + + if ((rs = mysql_store_result(db)) == NULL) + { + log_error("Get article data failed"); + ret = -2; + goto cleanup; + } + + if ((row = mysql_fetch_row(rs))) + { + uid = atoi(row[0]); + tid = atoi(row[1]); + sid = atoi(row[2]); + transship = (int8_t)atoi(row[3]); + excerption = (int8_t)atoi(row[4]); + } + else + { + log_error("Article not found: aid=%d", aid); + ret = -2; + goto cleanup; + } + mysql_free_result(rs); + rs = NULL; + + if (p_section->sid != sid) + { + log_error("Inconsistent SID of article (aid=%d): %d!=%d", aid, p_section->sid, sid); + ret = -2; + goto cleanup; + } + + // Check if already set + if (excerption != set) + { + snprintf(sql, sizeof(sql), + "UPDATE bbs SET excerption = %d WHERE AID = %d", + set, aid); + if (mysql_query(db, sql) != 0) + { + log_error("Set excerption error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + + // Clear gen_ex if unset excerption + if (set == 0) + { + snprintf(sql, sizeof(sql), + "UPDATE bbs SET gen_ex = 0, static = 0 WHERE AID = %d OR TID = %d", + aid, aid); + + if (mysql_query(db, sql) != 0) + { + log_error("Set gen_ex error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + + // Delete ex_dir path if head of thread + if (tid == 0) + { + snprintf(sql, sizeof(sql), + "DELETE FROM ex_file WHERE AID = %d", + aid); + + if (mysql_query(db, sql) != 0) + { + log_error("Delete ex_file error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + } + } + + // Change UID of attachments + snprintf(sql, sizeof(sql), + "UPDATE upload_file SET UID = %d " + "WHERE ref_AID = %d AND deleted = 0", + (set ? 0 : uid), aid); + if (mysql_query(db, sql) != 0) + { + log_error("Set attachment status error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + + // Modify exp + if (checkpriv(&BBS_priv, sid, S_GETEXP)) // Except in test section + { + snprintf(sql, sizeof(sql), + "UPDATE user_pubinfo SET exp = exp + %d WHERE UID = %d", + (set ? 1 : -1) * (tid == 0 ? (transship ? 20 : 50) : 10), uid); + + if (mysql_query(db, sql) != 0) + { + log_error("Change exp error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + } + + // Add log + snprintf(sql, sizeof(sql), + "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip) " + "VALUES(%d, %d, '%c', NOW(), '%s')", + aid, BBS_priv.uid, (set ? 'E' : 'O'), hostaddr_client); + + if (mysql_query(db, sql) != 0) + { + log_error("Add log error: %s", mysql_error(db)); + ret = -2; + goto cleanup; + } + } + + if (section_list_rw_lock(p_section) < 0) + { + log_error("section_list_rw_lock(sid=%d) error", sid); + ret = -3; + goto cleanup; + } + + ret = section_list_set_article_excerption(p_section, aid, set); + if (ret < 0) + { + log_error("section_list_set_article_excerption(sid=%d, aid=%d, set=%d) error", sid, aid, set); + ret = -3; + goto cleanup; + } + + if (section_list_rw_unlock(p_section) < 0) + { + log_error("section_list_rw_unlock(sid=%d) error", sid); + ret = -3; + goto cleanup; + } + + // Commit transaction + if (mysql_query(db, "COMMIT") != 0) + { + log_error("Mysqli error: %s", mysql_error(db)); + + // Rollback change to avoid data inconsistency + if (section_list_rw_lock(p_section) < 0) + { + log_error("section_list_rw_lock(sid=%d) error", sid); + ret = -3; + goto cleanup; + } + + ret = section_list_set_article_excerption(p_section, aid, excerption); + if (ret < 0) + { + log_error("section_list_set_article_excerption(sid=%d, aid=%d, set=%d) error", sid, aid, excerption); + ret = -3; + goto cleanup; + } + + if (section_list_rw_unlock(p_section) < 0) + { + log_error("section_list_rw_unlock(sid=%d) error", sid); + ret = -3; + goto cleanup; + } + + ret = 0; + } + else + { + ret = 1; // Success + } + +cleanup: + mysql_free_result(rs); + mysql_close(db); + + return ret; +} diff --git a/src/bbs_net.c b/src/bbs_net.c index bf34d14..2501f5b 100644 --- a/src/bbs_net.c +++ b/src/bbs_net.c @@ -45,12 +45,14 @@ #include #endif -static const char MENU_CONF_DELIM[] = " \t\r\n"; - enum _bbs_net_constant_t { MAXSTATION = 26 * 2, STATION_PER_LINE = 4, + ORG_NAME_MAX_LEN = 40, + SITE_NAME_MAX_LEN = 40, + HOSTNAME_MAX_LEN = 253, + PORT_STR_MAX_LEN = 5, USERNAME_MAX_LEN = 20, PASSWORD_MAX_LEN = 20, REMOTE_CONNECT_TIMEOUT = 10, // seconds @@ -60,25 +62,30 @@ enum _bbs_net_constant_t struct _bbsnet_conf { - char org_name[40]; - char site_name[40]; - char host_name[IP_ADDR_LEN]; - char port[6]; + char org_name[ORG_NAME_MAX_LEN + 1]; + char site_name[SITE_NAME_MAX_LEN + 1]; + char host_name[HOSTNAME_MAX_LEN + 1]; + char port[PORT_STR_MAX_LEN + 1]; int8_t use_ssh; char charset[CHARSET_MAX_LEN + 1]; } bbsnet_conf[MAXSTATION]; +static const char MENU_CONF_DELIM[] = " \t\r\n"; + static MENU_SET bbsnet_menu; static void unload_bbsnet_conf(void); static int load_bbsnet_conf(const char *file_config) { - FILE *fp; + FILE *fin; + int fin_line = 0; MENU *p_menu; MENU_ITEM *p_menu_item; MENU_ITEM_ID menu_item_id; - char line[LINE_BUFFER_LEN], *t1, *t2, *t3, *t4, *t5, *t6, *saveptr; + char line[LINE_BUFFER_LEN]; + char *p = NULL; + char *saveptr = NULL; long port; char *endptr; @@ -108,54 +115,92 @@ static int load_bbsnet_conf(const char *file_config) p_menu->title.show = 0; p_menu->screen_show = 0; - fp = fopen(file_config, "r"); - if (fp == NULL) + fin = fopen(file_config, "r"); + if (fin == NULL) { unload_bbsnet_conf(); return -2; } menu_item_id = 0; - while (fgets(line, sizeof(line), fp) && menu_item_id < MAXSTATION) + while (fgets(line, sizeof(line), fin) && menu_item_id < MAXSTATION) { - t1 = strtok_r(line, MENU_CONF_DELIM, &saveptr); - t2 = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); - t3 = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); - t4 = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); - t5 = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); - t6 = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); - - if (t1 == NULL || t2 == NULL || t3 == NULL || t4 == NULL || - t5 == NULL || t6 == NULL || t1[0] == '#') + fin_line++; + + p = strtok_r(line, MENU_CONF_DELIM, &saveptr); + if (p == NULL) // Blank line { continue; } - strncpy(bbsnet_conf[menu_item_id].site_name, t2, sizeof(bbsnet_conf[menu_item_id].site_name) - 1); - bbsnet_conf[menu_item_id].site_name[sizeof(bbsnet_conf[menu_item_id].site_name) - 1] = '\0'; - strncpy(bbsnet_conf[menu_item_id].org_name, t1, sizeof(bbsnet_conf[menu_item_id].org_name) - 1); + if (*p == '#' || *p == '\r' || *p == '\n') // Comment or blank line + { + continue; + } + + if (strlen(p) > sizeof(bbsnet_conf[menu_item_id].org_name) - 1) + { + log_error("Error org_name in BBSNET config line %d", fin_line); + continue; + } + strncpy(bbsnet_conf[menu_item_id].org_name, p, sizeof(bbsnet_conf[menu_item_id].org_name) - 1); bbsnet_conf[menu_item_id].org_name[sizeof(bbsnet_conf[menu_item_id].org_name) - 1] = '\0'; - strncpy(bbsnet_conf[menu_item_id].host_name, t3, sizeof(bbsnet_conf[menu_item_id].host_name) - 1); + + p = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); + if (p == NULL || strlen(p) > sizeof(bbsnet_conf[menu_item_id].site_name) - 1) + { + log_error("Error site_name in BBSNET config line %d", fin_line); + continue; + } + strncpy(bbsnet_conf[menu_item_id].site_name, p, sizeof(bbsnet_conf[menu_item_id].site_name) - 1); + bbsnet_conf[menu_item_id].site_name[sizeof(bbsnet_conf[menu_item_id].site_name) - 1] = '\0'; + + p = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); + if (p == NULL || strlen(p) > sizeof(bbsnet_conf[menu_item_id].host_name) - 1) + { + log_error("Error host_name in BBSNET config line %d", fin_line); + continue; + } + strncpy(bbsnet_conf[menu_item_id].host_name, p, sizeof(bbsnet_conf[menu_item_id].host_name) - 1); bbsnet_conf[menu_item_id].host_name[sizeof(bbsnet_conf[menu_item_id].host_name) - 1] = '\0'; - port = strtol(t4, &endptr, 10); + + p = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); + if (p == NULL || strlen(p) > sizeof(bbsnet_conf[menu_item_id].port) - 1) + { + log_error("Error port in BBSNET config line %d", fin_line); + continue; + } + port = strtol(p, &endptr, 10); if (*endptr != '\0' || port <= 0 || port > 65535) { - log_error("Invalid port value %ld of menu item %d", port, menu_item_id); - fclose(fp); - unload_bbsnet_conf(); - return -3; + log_error("Invalid port %ld in BBSNET config line %d", port, fin_line); + continue; } - strncpy(bbsnet_conf[menu_item_id].port, t4, sizeof(bbsnet_conf[menu_item_id].port) - 1); + strncpy(bbsnet_conf[menu_item_id].port, p, sizeof(bbsnet_conf[menu_item_id].port) - 1); bbsnet_conf[menu_item_id].port[sizeof(bbsnet_conf[menu_item_id].port) - 1] = '\0'; - bbsnet_conf[menu_item_id].use_ssh = (toupper(t5[0]) == 'Y'); - strncpy(bbsnet_conf[menu_item_id].charset, t6, sizeof(bbsnet_conf[menu_item_id].charset) - 1); + + p = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); + if (p == NULL || strlen(p) != 1) + { + log_error("Error use_ssh in BBSNET config line %d", fin_line); + continue; + } + bbsnet_conf[menu_item_id].use_ssh = (toupper(p[0]) == 'Y'); + + p = strtok_r(NULL, MENU_CONF_DELIM, &saveptr); + if (p == NULL || strlen(p) > sizeof(bbsnet_conf[menu_item_id].charset) - 1) + { + log_error("Error charset in BBSNET config line %d", fin_line); + continue; + } + strncpy(bbsnet_conf[menu_item_id].charset, p, sizeof(bbsnet_conf[menu_item_id].charset) - 1); bbsnet_conf[menu_item_id].charset[sizeof(bbsnet_conf[menu_item_id].charset) - 1] = '\0'; p_menu_item = get_menu_item_by_id(&bbsnet_menu, menu_item_id); if (p_menu_item == NULL) { - log_error("get_menu_item_by_id(%d) return NULL pointer", menu_item_id); - fclose(fp); + log_error("get_menu_item_by_id(%d) error: NULL pointer", menu_item_id); + fclose(fin); unload_bbsnet_conf(); return -3; } @@ -182,7 +227,7 @@ static int load_bbsnet_conf(const char *file_config) bbsnet_menu.menu_item_pos[0] = 0; bbsnet_menu.choose_step = 0; - fclose(fp); + fclose(fin); return 0; } @@ -359,8 +404,9 @@ static int bbsnet_connect(int n) } moveto(1, 1); - prints("通过SSH方式连接[%s]...", bbsnet_conf[n].site_name); - moveto(2, 1); + prints("\033[1;32m正在准备往 %s (%s:%s) 的%s连接... \033[m\r\n", + bbsnet_conf[n].site_name, bbsnet_conf[n].host_name, bbsnet_conf[n].port, + (bbsnet_conf[n].use_ssh ? "SSH" : "Telnet")); prints("请输入用户名: "); iflush(); if (str_input(remote_user, sizeof(remote_user), DOECHO) < 0) @@ -388,8 +434,9 @@ static int bbsnet_connect(int n) clearscr(); moveto(1, 1); - prints("\033[1;32m正在测试往 %s (%s) 的连接,请稍候... \033[m\r\n", - bbsnet_conf[n].site_name, bbsnet_conf[n].host_name); + prints("\033[1;32m正在测试往 %s (%s:%s) 的%s连接,请稍候... \033[m\r\n", + bbsnet_conf[n].site_name, bbsnet_conf[n].host_name, bbsnet_conf[n].port, + (bbsnet_conf[n].use_ssh ? "SSH" : "Telnet")); prints("\033[1;32m连接进行中,按\033[1;33mCtrl+C\033[1;32m中断。\033[m\r\n"); progress_bar(0, PROGRESS_BAR_LEN); diff --git a/src/login.c b/src/login.c index 3448006..bdd967c 100644 --- a/src/login.c +++ b/src/login.c @@ -324,24 +324,29 @@ int check_user(const char *username, const char *password) goto cleanup; } - if (!SSH_v2 && checklevel2(&BBS_priv, P_MAN_S)) - { - prints("\033[1;31m非普通账户必须使用SSH方式登录\033[m\r\n"); - ret = 1; - goto cleanup; - } - ret = load_user_info(db, BBS_uid); switch (ret) { case 0: // Login successfully + if (!SSH_v2 && checklevel2(&BBS_priv, P_MAN_S)) + { + prints("\033[1;31m非普通账户必须使用SSH方式登录\033[m\r\n"); + ret = 1; + goto cleanup; + } break; case -1: // Load data error prints("\033[1;31m读取用户数据错误...\033[m\r\n"); ret = -1; goto cleanup; case -2: // Enforce update user agreement + if (!SSH_v2 && checklevel2(&BBS_priv, P_MAN_S)) + { + prints("\033[1;31m非普通账户必须使用SSH方式登录\033[m\r\n"); + ret = 1; + goto cleanup; + } ret = 2; goto cleanup; case -3: // Dead diff --git a/src/main.c b/src/main.c index 7ba0952..51169b0 100644 --- a/src/main.c +++ b/src/main.c @@ -34,25 +34,78 @@ #include #include -static inline void app_help(void) +typedef void (*arg_option_handler_t)(void); + +struct arg_option_t +{ + const char *short_arg; + const char *long_arg; + const char *description; + arg_option_handler_t handler; +}; +typedef struct arg_option_t ARG_OPTION; + +static void arg_foreground(void); +static void arg_help(void); +static void arg_version(void); +static void arg_display_log(void); +static void arg_display_error_log(void); +static void arg_compile_info(void); +static void arg_error(void); + +static const ARG_OPTION arg_options[] = { + {"-f", "--foreground", "Run in foreground", arg_foreground}, + {"-h", "--help", "Display help message", arg_help}, + {"-v", "--version", "Display version information", arg_version}, + {NULL, "--display-log", "Display standard log information", arg_display_log}, + {NULL, "--display-error-log", "Display error log information", arg_display_error_log}, + {"-C", "--compile-config", "Display compile configuration", arg_compile_info}}; + +static const int arg_options_count = sizeof(arg_options) / sizeof(ARG_OPTION); + +static int daemon_service = 1; +static int std_log_redir = 0; +static int error_log_redir = 0; + +static void arg_foreground(void) { - fprintf(stderr, "Usage: bbsd [-fhv] [...]\n\n" - "-f\t--foreground\t\tForce program run in foreground\n" - "-h\t--help\t\t\tDisplay this help message\n" - "-v\t--version\t\tDisplay version information\n" - "\t--display-log\t\tDisplay standard log information\n" - "\t--display-error-log\tDisplay error log information\n" - "-C\t--compile-config\tDisplay compile configuration\n" - "\n If meet any bug, please report to \n\n"); + daemon_service = 0; } -static inline void arg_error(void) +static void arg_help(void) { - fprintf(stderr, "Invalid arguments\n"); - app_help(); + fprintf(stderr, "Usage: bbsd [-fhv] [...]\n\n"); + + for (int i = 0; i < arg_options_count; i++) + { + fprintf(stderr, "%s%*s%s%*s%s\n", + arg_options[i].short_arg ? arg_options[i].short_arg : "", + 8 - (arg_options[i].short_arg ? (int)strlen(arg_options[i].short_arg) : 0), "", + arg_options[i].long_arg ? arg_options[i].long_arg : "", + 24 - (arg_options[i].long_arg ? (int)strlen(arg_options[i].long_arg) : 0), "", + arg_options[i].description ? arg_options[i].description : ""); + } + + fprintf(stderr, "\n If meet any bug, please report to \n\n"); +} + +static void arg_version(void) +{ + printf("%s\n", APP_INFO); + printf("%s\n", COPYRIGHT_INFO); +} + +static void arg_display_log(void) +{ + std_log_redir = 1; +} + +static void arg_display_error_log(void) +{ + error_log_redir = 1; } -static inline void app_compile_info(void) +static void arg_compile_info(void) { printf("%s\n" "--enable-shared\t\t[%s]\n" @@ -95,12 +148,15 @@ static inline void app_compile_info(void) ); } +static void arg_error(void) +{ + fprintf(stderr, "Invalid arguments\n"); + arg_help(); +} + int main(int argc, char *argv[]) { char file_path_temp[FILE_PATH_LEN]; - int daemon = 1; - int std_log_redir = 0; - int error_log_redir = 0; FILE *fp; int ret; int last_aid; @@ -112,72 +168,28 @@ int main(int argc, char *argv[]) // Parse args for (i = 1; i < argc; i++) { - switch (argv[i][0]) + for (j = 0; j < arg_options_count; j++) { - case '-': - if (argv[i][1] != '-') + if (arg_options[j].short_arg && strcmp(argv[i], arg_options[j].short_arg) == 0) { - for (j = 1; j < strlen(argv[i]); j++) - { - switch (argv[i][j]) - { - case 'f': - daemon = 0; - break; - case 'h': - app_help(); - return 0; - case 'v': - printf("%s\n", APP_INFO); - printf("%s\n", COPYRIGHT_INFO); - return 0; - case 'C': - app_compile_info(); - return 0; - default: - arg_error(); - return 1; - } - } + arg_options[j].handler(); + break; } - else + else if (arg_options[j].long_arg && strcmp(argv[i], arg_options[j].long_arg) == 0) { - if (strcmp(argv[i] + 2, "foreground") == 0) - { - daemon = 0; - break; - } - if (strcmp(argv[i] + 2, "help") == 0) - { - app_help(); - return 0; - } - if (strcmp(argv[i] + 2, "version") == 0) - { - printf("%s\n", APP_INFO); - printf("%s\n", COPYRIGHT_INFO); - return 0; - } - if (strcmp(argv[i] + 2, "display-log") == 0) - { - std_log_redir = 1; - } - if (strcmp(argv[i] + 2, "display-error-log") == 0) - { - error_log_redir = 1; - } - if (strcmp(argv[i] + 2, "compile-config") == 0) - { - app_compile_info(); - return 0; - } + arg_options[j].handler(); + break; } - break; + } + if (j == arg_options_count) + { + arg_error(); + return -1; } } // Initialize daemon - if (daemon) + if (daemon_service) { init_daemon(); } @@ -210,11 +222,11 @@ int main(int argc, char *argv[]) return -1; } - if ((!daemon) && std_log_redir) + if ((!daemon_service) && std_log_redir) { log_common_redir(STDERR_FILENO); } - if ((!daemon) && error_log_redir) + if ((!daemon_service) && error_log_redir) { log_error_redir(STDERR_FILENO); } diff --git a/src/section_list_display.c b/src/section_list_display.c index e66c863..d4aadd3 100644 --- a/src/section_list_display.c +++ b/src/section_list_display.c @@ -58,6 +58,7 @@ enum select_cmd_t QUERY_USER, SET_FAVOR_ARTICLE, UNSET_FAVOR_ARTICLE, + SET_EXCERPTION_ARTICLE, FIRST_TOPIC_ARTICLE, LAST_TOPIC_ARTICLE, LAST_SECTION_ARTICLE, @@ -341,6 +342,12 @@ static enum select_cmd_t section_list_select(int total_page, int item_count, int return UNSET_FAVOR_ARTICLE; } break; + case 'm': + if (item_count > 0) + { + return SET_EXCERPTION_ARTICLE; + } + break; case KEY_HOME: *p_page_id = 0; case 'P': @@ -1093,6 +1100,17 @@ int section_list_display(const char *sname, int32_t aid) p_articles[selected_index]->tid == 0 ? p_articles[selected_index]->aid : p_articles[selected_index]->tid); } break; + case SET_EXCERPTION_ARTICLE: + if (checkpriv(&BBS_priv, p_section->sid, S_POST | S_MAN_S)) + { + ret = article_excerption_set(p_section, p_articles[selected_index]->aid, (p_articles[selected_index]->excerption ? 0 : 1)); + if (ret < 0) + { + log_error("article_excerption_set(sid=%d, aid=%d, set=%d) error\n", + p_section->sid, p_articles[selected_index]->aid, (p_articles[selected_index]->excerption ? 0 : 1)); + } + } + break; case FIRST_TOPIC_ARTICLE: case LAST_TOPIC_ARTICLE: page_id_cur = page_id;