月度归档:2020年07月

那个充满激情的岁月

那些年,我们一些为了BBS(网上论坛)而疯狂。为此,曾经刷在线时间(类似QQ刷太阳等级),动手在服务器上写了一个程序。作为留念,现决定低调开源。

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>

#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
//#include <iconv.h>

#define LOCKFILE "/var/run/bbs4gzhu.pid"

#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)


int                 lockfile;                  /* 锁文件的描述字 */
int 		    exit_flag = 0;


#define BUFFER_SIZE	409600
#define FORMHASH_SIZE	16
#define USERNAME_SIZE	128
#define PASSWORD_SIZE	128
#define SID_SIZE	64
#define AUTH_SIZE	128

int		    background = 0;
int 		    interval = 15;
char 		   *cookiepath = "";
char 		   username[USERNAME_SIZE] = "";
char 		   password[PASSWORD_SIZE] = "";
char		   *host = "bbs.gzhu.edu.cn";
short 		   port = 80;


static void
flock_reg ()
{
    char buf[16];
    struct flock fl;
    fl.l_start = 0;
    fl.l_whence = SEEK_SET;
    fl.l_len = 0;
    fl.l_type = F_WRLCK;
    fl.l_pid = getpid();
 
    //阻塞式的加锁
    if (fcntl (lockfile, F_SETLKW, &fl) < 0){
        perror ("fcntl_reg");
        exit(EXIT_FAILURE);
    }
 
    //把pid写入锁文件
    ftruncate (lockfile, 0);    
    sprintf (buf, "%ld", (long)getpid());
    write (lockfile, buf, strlen(buf) + 1);
}

void
daemon_init(void)
{
	pid_t	pid;
    	int     fd0;

	if ( (pid = fork()) < 0)
	    perror ("Fork");
	else if (pid != 0) {
        	fprintf(stdout, "bbs4gzhu&&Info: Forked background with PID: [%d]\n\n", pid);
		exit(EXIT_SUCCESS);
    	}
	setsid();	/* become session leader */
	chdir("/tmp");		/* change working directory */
	umask(0);		/* clear our file mode creation mask */
    flock_reg ();

    fd0 = open ("/dev/null", O_RDWR);
    dup2 (fd0, STDIN_FILENO);
    dup2 (fd0, STDERR_FILENO);
    dup2 (fd0, STDOUT_FILENO);
    close (fd0);
}


static int 
is_running()
{
    struct flock fl;
    fl.l_start = 0;
    fl.l_whence = SEEK_SET;
    fl.l_len = 0;
    fl.l_type = F_WRLCK;
 
    //尝试获得文件锁
    if (fcntl (lockfile, F_GETLK, &fl) < 0){
        perror ("fcntl_get");
        exit(EXIT_FAILURE);
    }

    if (exit_flag) {
        if (fl.l_type != F_UNLCK) {
            if ( kill (fl.l_pid, SIGINT) == -1 )
            perror("kill");
            fprintf (stdout, "bbs4gzhu&&Info: Kill Signal Sent to PID %d.\n", fl.l_pid);
        }
        else 
            fprintf (stderr, "bbs4gzhu&&Info: Program not running.\n");
        exit (EXIT_FAILURE);
    }


    //没有锁,则给文件加锁,否则返回锁着文件的进程pid
    if (fl.l_type == F_UNLCK) {
        flock_reg ();
        return 0;
    }

    return fl.l_pid;
}
static void
signal_interrupted (int signo)
{
    exit (EXIT_SUCCESS);
    exit_flag = 1;
    fprintf(stdout,"\nbbs4gzhu&&Info: Interrupted. \n");
}
static void show_usage()
{
	printf("auto online for bbs.gzhu.edu.cn \n\
options: \n \
	-b --background. run in background. mostly this is for service mode. \n\
	-e --exit. quit the running program in background \n\
	-i --interval value(in second). interval time between online request. default value is 15 seconds. \n\
	-h --help. show this usage message. \n\
	-c --cookiepath. specifies the file containing the cookies for online request. \n\
	-u --username. username for login in the bbs. \n\
	-p --password. md5 of the password, used for login in the bbs. \n\
	--host. host for auto online. default value is \"bbs.gzhu.edu.cn\" \n\
	--port. the port with site. default value is 80. \n");


}

static void 
init_arguments(int *argc, char ***argv)
{
    /* Option struct for progrm run arguments */
    static struct option long_options[] =
        {
        {"background",  no_argument,        &background,    'b'},       
        {"exit",      no_argument,  &exit_flag,              'e'},
        {"interval",         required_argument,  &interval,              'i'},
        {"help",         no_argument,  0,              'h'},
        {"cookiepath",         required_argument,  NULL,              'c'},
	{"username", required_argument, NULL, 'u'},
	{"password", required_argument, NULL, 'p'},
	{"host", required_argument, NULL, 2},
	{"port", required_argument, NULL, 1},
        {0, 0, 0, 0}
        };

    int c;
    while (1) {

        /* getopt_long stores the option index here. */
        int option_index = 0;
        c = getopt_long ((*argc), (*argv), "bei:hc:u:p:",
                        long_options, &option_index);
        if (c == -1)
            break;
        switch (c) {
            case 0:
               break;
            case 'b':
                background = 1;
                break;
            case 'e':
                exit_flag = 1;
                break;
	    case 'i':
                interval = atoi(optarg);
                break;
            case 'h':
                show_usage();
                exit(EXIT_SUCCESS);
                break;
	    case 'c':
		cookiepath = optarg;
		break;
	    case 'u':
		strncpy(username, optarg, USERNAME_SIZE);
		break;
	    case 'p':
		strncpy(password, optarg, PASSWORD_SIZE);
		break;
	    case 2:
		host = optarg;
		break;
	    case 1:
		port = atoi(optarg);
		break;
            case '?':               
                exit(EXIT_FAILURE);
                break;
            default:
                fprintf (stderr,"Unknown option character `\\x%x'.\n", c);
                exit(EXIT_FAILURE);
        }
    }
}

void PANIC(char *msg);
#define PANIC(msg)  {perror(msg); abort();}
int getitem(const char *str, char *buffer, const int size, const char *start, const char end);

int tcp_connect(const char *host, const short port);
struct sockaddr_in getaddr(const char *host, const short port);
void loadcookies(const char *filename, char *buffer, const int size);
int 
code_convert(char *from_charset, char *to_charset,
             char *inbuf, size_t inlen, char *outbuf, size_t outlen);

int parse_username(const char *str, char *buffer, const int size);
int parse_formhash(const char *str, char *buffer, const int size);
int parse_sid(const char *str, char *buffer, const int size);
int parse_auth(const char *str, char *buffer, const int size);
int parse_parse_cookies(const char *str, char *buffer, const int size);
int parse_welcome_msg(const char *str, char *buffer, const int size);
int parse_return_msg(const char *str, char *buffer, const int size);
int parse_error_msg(const char *str, char *buffer, const int size);

int login(const char *username, const char *password, char *cookies, const int size);
int send_request(const int sockfd, const char *url, const char *header, const char *data, char *buffer, const int size);
int check_http(const char *response, const int size);
int online(const char *cookies);
char *gettime();

int
main(int argc, char *argv[])
{
	int ins_pid;
	char cookies[BUFFER_SIZE];

	init_arguments(&argc, &argv);

	lockfile = open (LOCKFILE, O_RDWR | O_CREAT , LOCKMODE);
	if (lockfile < 0){
		perror ("Lockfile");
		exit(EXIT_FAILURE);
	}

	if ( (ins_pid = is_running()) ) {
		fprintf(stderr,"bbs4gzhu@@ERROR: Program already "
				    "running with PID %d\n", ins_pid);
		exit(EXIT_SUCCESS);
	}

	signal (SIGINT, signal_interrupted);
	signal (SIGTERM, signal_interrupted);

	bzero(cookies, sizeof(cookies));
	if(strlen(cookiepath) > 0) {
		loadcookies(cookiepath, cookies, sizeof(cookies));
		if(strlen(username) <= 0) {
			if(parse_username(cookies, username, sizeof(username)) <= 0) {
				if(parse_sid(cookies, username, sizeof(username)) <= 0) {
					strcpy(username, "unknown");
				}
			}
		}
	} else {
		if(strlen(username) <= 0) {
			strcpy(username, "nobody");
		}
	}

	printf("current username: %s \n", username);

	if(background)    daemon_init();

	while(1) {
		printf("%s logging...", gettime());
		fflush(stdout);
		if(!login(username, password, cookies, sizeof(cookies))) {
			sleep(interval);
			continue;
		}
		printf("\n");
		while(online(cookies)) {
			printf("%s online...\r", gettime()); fflush(stdout);
			sleep(interval);
		}
		printf("%s offline...it will auto retrying!\n", gettime());
	}

	return 0;
}/* ----------  end of function main  ---------- */
char *gettime()
{
	static char str[64];
	struct tm *local;
	time_t t;
	t = time(NULL);
	local=localtime(&t);
	sprintf(str, "%02d:%02d:%02d", local->tm_hour, local->tm_min, local->tm_sec);
	return str;
}
int check_http(const char *response, const int size)
{
	char buffer[16];
	if(size <= 16) return 0;
	if(strstr(response, "HTTP/1.1 200 OK\r\n") == NULL) {
		strncpy(buffer, response, 15);
		buffer[15] = 0;
		printf("%s bad status:%s\n", username, buffer);
		return 0;
	}
	return 1;
}

void loadcookies(const char *filename, char *buffer, const int size)
{
	int bytes_read;
	FILE *fd = fopen(filename, "r");
	if(fd == NULL) {
		PANIC("loadcookies");
	}
	bytes_read = fread(buffer, 1, size, fd);
	buffer[bytes_read - 1] = 0;
	
	while(bytes_read--) {
		if(buffer[bytes_read] == '\r') buffer[bytes_read] = ' ';
		if(buffer[bytes_read] == '\n') buffer[bytes_read] = ' ';
	}
	
	fclose(fd);
}

int send_request(const int sockfd, const char *url, const char *header, const char *data, char *buffer, const int size) 
{
	int bytes_read = 0;
	char request_buffer[BUFFER_SIZE];
	if(data != NULL) {
		sprintf(request_buffer, "POST %s HTTP/1.1\r\nHost: %s:%d\r\nReferer: http://%s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\n%s\r\n\r\n%s", url, host, port, host, strlen(data), header, data);
	} else {
		sprintf(request_buffer, "GET %s HTTP/1.1\r\nHost: %s:%d\r\nReferer: http://%s\r\n%s\r\n", url, host, port, host, header);
	}

	//printf("%s\n", request_buffer);

	/* 发送数据 */
	send(sockfd, request_buffer, strlen(request_buffer), 0);

	/* 接收缓存 */
	bzero(buffer, sizeof(size));

	/* 接收 */
	bytes_read = recv(sockfd, buffer, size, 0);

	//printf("%s\n", buffer);

	return bytes_read;
}

int login(const char *username, const char *password, char *cookies, const int size) 
{
	int sockfd;
	int bytes_read;
	char buffer[BUFFER_SIZE];
	char header[BUFFER_SIZE];
	char formhash[FORMHASH_SIZE];
	char formdata[BUFFER_SIZE];
	char sid[SID_SIZE];
	char auth[AUTH_SIZE];
	char msg[BUFFER_SIZE];

	/* 先获取登录页面 */
	sockfd = tcp_connect(host, port);
	bytes_read = send_request(sockfd, "/logging.php?action=login&inajax=1", "", NULL, buffer, sizeof(buffer));	
	close(sockfd);
	if(check_http(buffer, bytes_read) == 0) return 0;
//	printf("%s\n", buffer);
	
	/* 若被列入黑名单,则等待3分钟后再试 */
	if(strlen(strstr(buffer, "\r\n\r\n")+4) <= 120) {//这个值一般在99左右。
		bytes_read = parse_error_msg(buffer, msg, sizeof(msg));
		if(bytes_read > 0 && strstr(msg, "\n") == NULL) {
			printf("Error:%s . Try 15m later..\r", msg);	
		} else {
			printf("Error:unknown. Try 3 minute later..\r", msg);
		}
		fflush(stdout);
		sleep(3*60);
		return 0;
	}
	/* 解析formhash */
	bytes_read = parse_formhash(buffer, formhash, sizeof(formhash));
	if(bytes_read <= 0) return 0;

	/* 解析SID */
	bytes_read = parse_sid(buffer, sid, sizeof(sid));
	if(bytes_read <= 0) return 0;

	strcat(cookies, "hLR_sid=");	strcat(cookies, sid);	strcat(cookies, ";");

	/* 构造header */
	sprintf(header, "Cookie: %s\r\n", cookies);

	/* 构造登录表单 */
	sprintf(formdata, "formhash=%s&loginfield=username&username=%s&password=%s&questionid=0&answer=&cookietime=2592000", formhash, username, password);

//	printf("%s %s\n", header, formdata);
	
	/* 提交登录请求 */
	sockfd = tcp_connect(host, port);
	bytes_read = send_request(sockfd, "/logging.php?action=login&loginsubmit=yes&inajax=1", header, formdata, buffer, sizeof(buffer));
	close(sockfd);
	if(check_http(buffer, bytes_read) == 0) return 0;

	/* 解析欢迎消息 */
	bytes_read = parse_welcome_msg(buffer, msg, sizeof(msg));
	if(bytes_read <= 0)  {
		/* 解析登录返回信息 */
		bytes_read = parse_return_msg(buffer, msg, sizeof(msg));
		if(bytes_read > 0) printf("Failed Info:%s\r", msg);
		return 0;
	}
	printf("Login Info:%s\r", msg);
	fflush(stdout);

	/* 解析auth */
	bytes_read = parse_auth(buffer, auth, sizeof(auth));
	if(bytes_read <= 0) return 0;	
	strcat(cookies, "hLR_auth=");	strcat(cookies, auth);	strcat(cookies, ";");

	/* 有效时间 */
	strcat(cookies, "hLR_cookietime=2592000;");

	return strlen(cookies);
}

int online(const char *cookies)
{
	int sockfd;
	int bytes_read;
	char buffer[BUFFER_SIZE];
	char header[BUFFER_SIZE];

	/* 构造header */
	sprintf(header, "Cookie: %s\r\n", cookies);

	/* 刷新页面保持在线 */
	sockfd = tcp_connect(host, port);
	bytes_read = send_request(sockfd, "/search.php", header, NULL, buffer, sizeof(buffer));	
	close(sockfd);
	if(check_http(buffer, bytes_read) == 0) return 0;
	
	/* 检测用户凭据是否到期 */
	if(strstr(buffer, "logging.php?action=logout") == NULL) return 0;

	return 1;
}

int parse_error_msg(const char *str, char *buffer, const int size)
{
	return	getitem(str, buffer, size, "![CDATA[", ']');
}

int parse_username(const char *str, char *buffer, const int size)
{
	return	getitem(str, buffer, size, "uchome_loginuser=", ';');
}

int parse_formhash(const char *str, char *buffer, const int size)
{
	return	getitem(str, buffer, size, "name=\"formhash\" value=\"", '\"');
}

int parse_sid(const char *str, char *buffer, const int size)
{
	return	getitem(str, buffer, size, "hLR_sid=", ';');
}

int parse_auth(const char *str, char *buffer, const int size)
{
	return	getitem(str, buffer, size, "hLR_auth=", ';');
}

int parse_cookies(const char *str, char *buffer, const int size)
{
	return	getitem(str, buffer, size, "Set-Cookie: ", ';');	
}

int parse_welcome_msg(const char *str, char *buffer, const int size)
{
	return getitem(str, buffer, size, "$('messageleft').innerHTML = '", '\'');	
}


int parse_return_msg(const char *str, char *buffer, const int size)
{
	return getitem(str, buffer, size, "<em id=\"returnmessage\">", '<');	
}


int getitem(const char *str, char *buffer, const int size, const char *start, const char end)
{
	int i;
	char *p = strstr(str, start);
	if(p == NULL) return 0;

	for(i = 0, p+= strlen(start); p[i] &&  i < (size - 1); i++) {
		if(p[i] == end) break;
		buffer[i] = p[i];
	}
	buffer[i] = '\0';
	return i;
}



struct sockaddr_in getaddr(const char *host, const short port)
{
    struct sockaddr_in sin;
    struct hostent *hosts = NULL;

    bzero(&sin, sizeof(struct sockaddr_in));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    if(inet_addr(host) == INADDR_NONE) {
        hosts = gethostbyname(host);
	if(hosts != NULL) {
	    sin.sin_addr = *((struct in_addr*)hosts->h_addr);
	}
    } else {
	sin.sin_addr.s_addr = inet_addr(host);
    }
    return sin;
}

int
tcp_connect(const char *host, const short port)
{

    int     sockfd;
    struct sockaddr_in endpoint;

    endpoint = getaddr(host, port);
  
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0) return 0;

    if (connect(sockfd, (struct sockaddr*)&endpoint, sizeof(struct sockaddr_in)) != 0) {
	close(sockfd); 
	return 0;
    }

    return (sockfd);
}

/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  code_convert
 *  Description:  字符串编码转换
 * =====================================================================================
 */
int 
code_convert(char *from_charset, char *to_charset,
             char *inbuf, size_t inlen, char *outbuf, size_t outlen)
{
    memcpy(outbuf, inbuf, inlen < outlen ? inlen : outlen);
/*
    iconv_t cd;

    cd = iconv_open(to_charset,from_charset);

    if (cd==0) 
      return -1;
    memset(outbuf,0,outlen);

    if (iconv (cd, &inbuf, &inlen, &outbuf, &outlen)==-1) 
      return -1;
    iconv_close(cd);
*/
    return 0;
}