<commit_before>int client_line(LOGGER log, CONNECTION* client, DATABASE* database){
	CLIENT* client_data=(CLIENT*)client->aux_data;

	logprintf(log, LOG_ALL_IO, ">> %s\n", client_data->recv_buffer);
	
	switch(client_data->state){
		case STATE_AUTH:
			return state_authorization(log, client, database);
		case STATE_TRANSACTION:
			return state_transaction(log, client, database);
		case STATE_UPDATE:
			return state_update(log, client, database);
	}
	
	return 0;
}

int client_accept(LOGGER log, CONNECTION* listener, CONNPOOL* clients){
	int client_slot=-1, flags;
	CLIENT empty_data = {
		.listener = listener,
		.recv_offset = 0,
		.state = STATE_AUTH,
		.maildrop = {
			.count = 0,
			.mails = NULL,
			.list_user = NULL,
			.fetch_user = NULL,
			.delete_user = NULL
		},
		.auth = {
			.method = AUTH_USER,
			.user = NULL,
			.authorized = false
		}
	};
	CLIENT* actual_data;
	LISTENER* listener_data=(LISTENER*)listener->aux_data;

	if(connpool_active(*clients)>=CMAIL_MAX_CONCURRENT_CLIENTS){
		logprintf(log, LOG_INFO, "Not accepting new client, limit reached\n");
		return 1;
	}
	
	client_slot=connpool_add(clients, accept(listener->fd, NULL, NULL));
	
	if(client_slot<0){
		logprintf(log, LOG_ERROR, "Failed to pool client socket\n");
		return -1;
	}

	//set socket nonblocking
	flags=fcntl(clients->conns[client_slot].fd, F_GETFL, 0);
	if(flags<0){
		flags=0;
	}
	//FIXME check errno
  	fcntl(clients->conns[client_slot].fd, F_SETFL, flags|O_NONBLOCK);

	if(!(clients->conns[client_slot].aux_data)){
		clients->conns[client_slot].aux_data=malloc(sizeof(CLIENT));
		if(!clients->conns[client_slot].aux_data){
			logprintf(log, LOG_ERROR, "Failed to allocate client data set\n");
			return -1;
		}
	}
	else{
		//preserve old data
		//TODO
	}

	//initialise / reset client data structure
	actual_data=(CLIENT*)clients->conns[client_slot].aux_data;
	*actual_data = empty_data;
	
	#ifndef CMAIL_NO_TLS
	//if on tlsonly port, immediately wait for negotiation
	if(listener->tls_mode==TLS_ONLY){
		logprintf(log, LOG_INFO, "Listen socket is TLSONLY, waiting for negotiation...\n");
		clients->conns[client_slot].tls_mode=TLS_NEGOTIATE;
		return tls_initclient(log, &(clients->conns[client_slot]), listener_data->tls_priorities, listener_data->tls_cert);
	}
	#endif
	
	client_send(log, &(clients->conns[client_slot]), "+OK %s POP3 ready\r\n", listener_data->announce_domain);
	return 0;
}

int client_close(LOGGER log, CONNECTION* client, DATABASE* database){
	CLIENT* client_data=(CLIENT*)client->aux_data;
	
	#ifndef CMAIL_NO_TLS
	//shut down the tls session
	if(client->tls_mode!=TLS_NONE){
		gnutls_bye(client->tls_session, GNUTLS_SHUT_RDWR);
		gnutls_deinit(client->tls_session);
		client->tls_mode=TLS_NONE;
	}
	#endif

	//close the socket
	close(client->fd);

	//reset client data
	maildrop_release(log, database, &(client_data->maildrop), client_data->auth.user);
	auth_reset(&(client_data->auth));
	
	//return the conpool slot
	client->fd=-1;
	
	return 0;
}

int client_process(LOGGER log, CONNECTION* client, DATABASE* database){
	CLIENT* client_data=(CLIENT*)client->aux_data;
	LISTENER* listener_data=(LISTENER*)client_data->listener->aux_data;
	ssize_t left, bytes, line_length;

	//TODO handle client timeout
	
	#ifndef CMAIL_NO_TLS
	do{
	#endif
	left=sizeof(client_data->recv_buffer)-client_data->recv_offset;
		
	if(left<2){
		//unterminated line
		//FIXME this might be kind of a harsh response
		logprintf(log, LOG_WARNING, "Line too long, closing client connection\n");
		client_send(log, client, "-ERR Line too long\r\n");
		client_close(log, client, database);
		return 0;
	}

	bytes=network_read(log, client, client_data->recv_buffer+client_data->recv_offset, left);

	//failed to read from socket
	if(bytes<0){
		#ifndef CMAIL_NO_TLS
		switch(client->tls_mode){
			case TLS_NONE:
		#endif
		switch(errno){
			case EAGAIN:
				logprintf(log, LOG_WARNING, "Read signaled, but blocked\n");
				return 0;
			default:
				logprintf(log, LOG_ERROR, "Failed to read from client: %s\n", strerror(errno));
				client_close(log, client, database);
				return -1;
		}
		#ifndef CMAIL_NO_TLS
			break;
			case TLS_NEGOTIATE:
				//errors during TLS negotiation
				if(bytes==-2){
					client_close(log, client, database);
				}
				return 0;
			case TLS_ONLY:
				switch(bytes){
					case GNUTLS_E_INTERRUPTED:
					case GNUTLS_E_AGAIN:
						logprintf(log, LOG_WARNING, "TLS read signaled, but blocked\n");
						return 0;
					default:
						logprintf(log, LOG_ERROR, "GnuTLS reported an error while reading: %s\n", gnutls_strerror(bytes));
						client_close(log, client, database);
						return -1;
				}
		}
		#endif
	}
	
	//client disconnect / handshake success
	else if(bytes==0){
		#ifndef CMAIL_NO_TLS
		switch(client->tls_mode){
			case TLS_NEGOTIATE:
				//tls handshake ok
				client->tls_mode=TLS_ONLY;
				if(client_data->listener->tls_mode==TLS_ONLY){
					//send greeting if listener is tlsonly
					client_send(log, client, "+OK %s POP3 ready\r\n", listener_data->announce_domain);
				}
				break;
			default:
		#endif
		logprintf(log, LOG_INFO, "Client has disconnected\n");
		client_close(log, client, database);
		return 0;
		#ifndef CMAIL_NO_TLS
		}
		#endif
	}

	logprintf(log, LOG_DEBUG, "Received %d bytes of data, recv_offset is %d\n", bytes, client_data->recv_offset);
	
	do{
		line_length=common_next_line(log, client_data->recv_buffer, &(client_data->recv_offset), &bytes);
		if(line_length>=0){
			if(line_length>=POP_MAX_LINE_LENGTH-2){
				logprintf(log, LOG_WARNING, "Line too long, ignoring\n");
				client_send(log, client, "-ERR Line too long\r\n");
				//client_line(log, client, database);
				//FIXME might handle this more sensibly
			}
			else{
				client_line(log, client, database);
			}
		}<commit_msg>fix read when tls handshake succeeded<commit_after>int client_line(LOGGER log, CONNECTION* client, DATABASE* database){
	CLIENT* client_data=(CLIENT*)client->aux_data;

	logprintf(log, LOG_ALL_IO, ">> %s\n", client_data->recv_buffer);
	
	switch(client_data->state){
		case STATE_AUTH:
			return state_authorization(log, client, database);
		case STATE_TRANSACTION:
			return state_transaction(log, client, database);
		case STATE_UPDATE:
			return state_update(log, client, database);
	}
	
	return 0;
}

int client_accept(LOGGER log, CONNECTION* listener, CONNPOOL* clients){
	int client_slot=-1, flags;
	CLIENT empty_data = {
		.listener = listener,
		.recv_offset = 0,
		.state = STATE_AUTH,
		.maildrop = {
			.count = 0,
			.mails = NULL,
			.list_user = NULL,
			.fetch_user = NULL,
			.delete_user = NULL
		},
		.auth = {
			.method = AUTH_USER,
			.user = NULL,
			.authorized = false
		}
	};
	CLIENT* actual_data;
	LISTENER* listener_data=(LISTENER*)listener->aux_data;

	if(connpool_active(*clients)>=CMAIL_MAX_CONCURRENT_CLIENTS){
		logprintf(log, LOG_INFO, "Not accepting new client, limit reached\n");
		return 1;
	}
	
	client_slot=connpool_add(clients, accept(listener->fd, NULL, NULL));
	
	if(client_slot<0){
		logprintf(log, LOG_ERROR, "Failed to pool client socket\n");
		return -1;
	}

	//set socket nonblocking
	flags=fcntl(clients->conns[client_slot].fd, F_GETFL, 0);
	if(flags<0){
		flags=0;
	}
	//FIXME check errno
  	fcntl(clients->conns[client_slot].fd, F_SETFL, flags|O_NONBLOCK);

	if(!(clients->conns[client_slot].aux_data)){
		clients->conns[client_slot].aux_data=malloc(sizeof(CLIENT));
		if(!clients->conns[client_slot].aux_data){
			logprintf(log, LOG_ERROR, "Failed to allocate client data set\n");
			return -1;
		}
	}
	else{
		//preserve old data
		//TODO
	}

	//initialise / reset client data structure
	actual_data=(CLIENT*)clients->conns[client_slot].aux_data;
	*actual_data = empty_data;
	
	#ifndef CMAIL_NO_TLS
	//if on tlsonly port, immediately wait for negotiation
	if(listener->tls_mode==TLS_ONLY){
		logprintf(log, LOG_INFO, "Listen socket is TLSONLY, waiting for negotiation...\n");
		clients->conns[client_slot].tls_mode=TLS_NEGOTIATE;
		return tls_initclient(log, &(clients->conns[client_slot]), listener_data->tls_priorities, listener_data->tls_cert);
	}
	#endif
	
	client_send(log, &(clients->conns[client_slot]), "+OK %s POP3 ready\r\n", listener_data->announce_domain);
	return 0;
}

int client_close(LOGGER log, CONNECTION* client, DATABASE* database){
	CLIENT* client_data=(CLIENT*)client->aux_data;
	
	#ifndef CMAIL_NO_TLS
	//shut down the tls session
	if(client->tls_mode!=TLS_NONE){
		gnutls_bye(client->tls_session, GNUTLS_SHUT_RDWR);
		gnutls_deinit(client->tls_session);
		client->tls_mode=TLS_NONE;
	}
	#endif

	//close the socket
	close(client->fd);

	//reset client data
	maildrop_release(log, database, &(client_data->maildrop), client_data->auth.user);
	auth_reset(&(client_data->auth));
	
	//return the conpool slot
	client->fd=-1;
	
	return 0;
}

int client_process(LOGGER log, CONNECTION* client, DATABASE* database){
	CLIENT* client_data=(CLIENT*)client->aux_data;
	LISTENER* listener_data=(LISTENER*)client_data->listener->aux_data;
	ssize_t left, bytes, line_length;

	//TODO handle client timeout
	
	#ifndef CMAIL_NO_TLS
	do{
	#endif
	left=sizeof(client_data->recv_buffer)-client_data->recv_offset;
		
	if(left<2){
		//unterminated line
		//FIXME this might be kind of a harsh response
		logprintf(log, LOG_WARNING, "Line too long, closing client connection\n");
		client_send(log, client, "-ERR Line too long\r\n");
		client_close(log, client, database);
		return 0;
	}

	bytes=network_read(log, client, client_data->recv_buffer+client_data->recv_offset, left);

	//failed to read from socket
	if(bytes<0){
		#ifndef CMAIL_NO_TLS
		switch(client->tls_mode){
			case TLS_NONE:
		#endif
		switch(errno){
			case EAGAIN:
				logprintf(log, LOG_WARNING, "Read signaled, but blocked\n");
				return 0;
			default:
				logprintf(log, LOG_ERROR, "Failed to read from client: %s\n", strerror(errno));
				client_close(log, client, database);
				return -1;
		}
		#ifndef CMAIL_NO_TLS
			break;
			case TLS_NEGOTIATE:
				//errors during TLS negotiation
				if(bytes==-2){
					client_close(log, client, database);
				}
				return 0;
			case TLS_ONLY:
				switch(bytes){
					case GNUTLS_E_INTERRUPTED:
					case GNUTLS_E_AGAIN:
						logprintf(log, LOG_WARNING, "TLS read signaled, but blocked\n");
						return 0;
					default:
						logprintf(log, LOG_ERROR, "GnuTLS reported an error while reading: %s\n", gnutls_strerror(bytes));
						client_close(log, client, database);
						return -1;
				}
		}
		#endif
	}
	
	//client disconnect / handshake success
	else if(bytes==0){
		#ifndef CMAIL_NO_TLS
		switch(client->tls_mode){
			case TLS_NEGOTIATE:
				//tls handshake ok
				client->tls_mode=TLS_ONLY;
				if(client_data->listener->tls_mode==TLS_ONLY){
					//send greeting if listener is tlsonly
					client_send(log, client, "+OK %s POP3 ready\r\n", listener_data->announce_domain);
				}
				break;
			default:
		#endif
		logprintf(log, LOG_INFO, "Client has disconnected\n");
		client_close(log, client, database);
		#ifndef CMAIL_NO_TLS
		}
		#endif
		return 0;
	}

	logprintf(log, LOG_DEBUG, "Received %d bytes of data, recv_offset is %d\n", bytes, client_data->recv_offset);
	
	do{
		line_length=common_next_line(log, client_data->recv_buffer, &(client_data->recv_offset), &bytes);
		if(line_length>=0){
			if(line_length>=POP_MAX_LINE_LENGTH-2){
				logprintf(log, LOG_WARNING, "Line too long, ignoring\n");
				client_send(log, client, "-ERR Line too long\r\n");
				//client_line(log, client, database);
				//FIXME might handle this more sensibly
			}
			else{
				client_line(log, client, database);
			}
		}
{'fraction_non_alphanumeric': 0.13227146814404434, 'fraction_numerical': 0.005020775623268698, 'mean_word_length': 4.368494423791821, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5141459', 'n_tokens_mistral': 4732, 'n_tokens_neox': 4166, 'n_words': 905}