Ejemplo n.º 1
0
void t07_multiline_headers(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok;
	
	onion_set_max_post_size(server, 1024);
	req=onion_request_new(custom_io);
	FAIL_IF_EQUAL(req,NULL);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);
	
	{
		const char *query="GET / HTTP/1.0\n"
											"Host: 127.0.0.1\n\rContent-Length: 24\n"
											"Other-Header: My header is very long and with several\n lines\n"
											"Extra-Other-Header: My header is very long and with several\n \n lines\n"
											"My-Other-Header: My header is very long and with several\n\tlines\n\n";
		
		ok=onion_request_write(req,query,strlen(query));
	}
	FAIL_IF_EQUAL(ok,OCS_INTERNAL_ERROR);
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"other-header"),"My header is very long and with several lines");
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"extra-other-header"),"My header is very long and with several lines");
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"My-other-header"),"My header is very long and with several lines");
	onion_request_clean(req);

	{
		const char *query="GET / HTTP/1.0\n"
											"Host: 127.0.0.1\n\rContent-Length: 24\n"
											"Other-Header: My header is very long and with several\n lines\n"
											"My-Other-Header: My header is very long and with several\nlines\n\n";
		
		ok=onion_request_write(req,query,strlen(query));
	}
	FAIL_IF_NOT_EQUAL(ok,OCS_INTERNAL_ERROR); // No \t at my-other-header
	
	onion_request_free(req);
	
	
	END_LOCAL();
}
Ejemplo n.º 2
0
static onion_connection_status ask_session(void *_, onion_request *req, onion_response *res){
  onion_dict *session=onion_request_get_session_dict(req);
  if (set_data_on_session)
    onion_dict_add(session,"Test","New data to create the session",0);
  has_set_cookie=0;
  onion_response_write0(res, "If I write before getting session, then there is no Set-Cookie.\n");
  onion_response_printf(res, "%d elements at the session.\n", onion_dict_count(session));
  ONION_DEBUG("Session ID is %s, cookies %s",req->session_id, onion_request_get_header(req, "Cookie"));
  strcpy(lastsessionid, req->session_id);
  
  return OCS_PROCESSED;
}
Ejemplo n.º 3
0
int onion_handler_auth_pam_handler(onion_handler_auth_pam_data *d, onion_request *request, onion_response *res){
	/// Use session to know if already logged in, so do not mess with PAM so often.
	if (onion_request_get_session(request, "pam_logged_in"))
		return onion_handler_handle(d->inside, request, res);
	
	const char *o=onion_request_get_header(request, "Authorization");
	char *auth=NULL;
	char *username=NULL;
	char *passwd=NULL;
	if (o && strncmp(o,"Basic",5)==0){
		//fprintf(stderr,"auth: '%s'\n",&o[6]);
		auth=onion_base64_decode(&o[6], NULL);
		username=auth;
		int i=0;
		while (auth[i]!='\0' && auth[i]!=':') i++;
		if (auth[i]==':'){
			auth[i]='\0'; // so i have user ready
			passwd=&auth[i+1];
		}
		else
			passwd=NULL;
	}
	
	// I have my data, try to authorize
	if (username && passwd){
		int ok=authorize(d->pamname, username, passwd);
		
		if (ok){ // I save the username at the session, so it can be accessed later.
			onion_dict *session=onion_request_get_session_dict(request);
			onion_dict_lock_write(session);
			onion_dict_add(session, "username", username, OD_REPLACE|OD_DUP_VALUE);
			onion_dict_add(session, "pam_logged_in", username, OD_REPLACE|OD_DUP_VALUE);
			onion_dict_unlock(session);
			
			free(auth);
			return onion_handler_handle(d->inside, request, res);
		}
	}
	if (auth)
		free(auth);

	
	// Not authorized. Ask for it.
	char temp[256];
	sprintf(temp, "Basic realm=\"%s\"",d->realm);
	onion_response_set_header(res, "WWW-Authenticate",temp);
	onion_response_set_code(res, HTTP_UNAUTHORIZED);
	onion_response_set_length(res,sizeof(RESPONSE_UNAUTHORIZED));
	
	onion_response_write(res,RESPONSE_UNAUTHORIZED,sizeof(RESPONSE_UNAUTHORIZED));
	return OCS_PROCESSED;
}
Ejemplo n.º 4
0
/**
 * @short Handles a propfind
 * 
 * @param path the shared path.
 */
onion_connection_status onion_webdav_propfind(const char *filename, onion_webdav *wd, onion_request* req, onion_response* res){
	// Prepare the basepath, necesary for props.
	char *basepath=NULL;
	int pathlen=0;
	const char *current_path=onion_request_get_path(req);
	const char *fullpath=onion_request_get_fullpath(req);
	pathlen=(current_path-fullpath);
	basepath=alloca(pathlen+1);
	memcpy(basepath, fullpath, pathlen+1);
	ONION_DEBUG0("Pathbase initial <%s> %d", basepath, pathlen); 
	while(basepath[pathlen]=='/' && pathlen>0)
		pathlen--;
	basepath[pathlen+1]=0;
				 
	ONION_DEBUG0("PROPFIND; pathbase %s", basepath);
	int depth;
	{
		const char *depths=onion_request_get_header(req, "Depth");
		if (!depths){
			ONION_ERROR("Missing Depth header on webdav request");
			return OCS_INTERNAL_ERROR;
		}
		if (strcmp(depths,"infinity")==0){
			ONION_ERROR("Infinity depth not supported yet.");
			return OCS_INTERNAL_ERROR;
		}
		depth=atoi(depths);
	}

	int props=onion_webdav_parse_propfind(onion_request_get_data(req));
	ONION_DEBUG("Asking for props %08X, depth %d", props, depth);
	
	onion_block *block=onion_webdav_write_propfind(basepath, filename, onion_request_get_path(req), depth, props);
	
	if (!block) // No block, resource does not exist
		return onion_shortcut_response("Not found", HTTP_NOT_FOUND, req, res);
		
	ONION_DEBUG0("Printing block %s", onion_block_data(block));
	
	onion_response_set_header(res, "Content-Type", "text/xml; charset=\"utf-8\"");
	onion_response_set_length(res, onion_block_size(block));
	onion_response_set_code(res, HTTP_MULTI_STATUS);
	onion_response_write_headers(res);
	onion_response_flush(res);
	
	onion_response_write(res, onion_block_data(block), onion_block_size(block));
	
	onion_block_free(block);
	
	return OCS_PROCESSED;
}
Ejemplo n.º 5
0
void t11_cookies(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok;
	
	req=onion_request_new(custom_io);
	FAIL_IF_EQUAL(req,NULL);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);
	
	{
		const char *query="GET / HTTP/1.0\n"
											"Content-Type: application/x-www-form-urlencoded\n"
											"Host: 127.0.0.1\n\r"
											"Cookie: key1=value1; key2=value2;\n"
											"Accept-Language: en\n"; // Missing \n caused memleak, to check with valgrind
		
		ok=onion_request_write(req,query,strlen(query));
	}
	FAIL_IF_EQUAL(ok,OCS_INTERNAL_ERROR);
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"Host"),"127.0.0.1");
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"Cookie"), "key1=value1; key2=value2;");
	
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_cookie(req,"key1"), "value1");
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_cookie(req,"key2"), "value2");
	FAIL_IF_EQUAL_STR(onion_request_get_cookie(req," key2"), "value2");
	
	onion_dict *cookies=onion_request_get_cookies_dict(req);
	FAIL_IF_EQUAL(cookies, NULL);
	
	FAIL_IF_NOT_EQUAL_STR(onion_dict_get(cookies,"key1"), "value1");
	FAIL_IF_NOT_EQUAL_STR(onion_dict_get(cookies,"key2"), "value2");
	
	onion_request_free(req);
	
	
	END_LOCAL();
}
Ejemplo n.º 6
0
/**
 * @short Moves a resource
 */
onion_connection_status onion_webdav_move(const char *filename, onion_webdav *wd, onion_request *req, onion_response *res){
	const char *dest=onion_request_get_header(req,"Destination");
	if (!dest)
		return OCS_INTERNAL_ERROR;
	const char *dest_orig=dest;
	// Skip the http... part. Just 3 /.
	int i;
	for (i=0;i<3;i+=(*dest++=='/'))
		if (*dest==0)
			return OCS_INTERNAL_ERROR;
	dest--;
	
	const char *fullpath=onion_request_get_fullpath(req);
	const char *partialpath=onion_request_get_path(req);
	// Not the fixed URL part for this handler.
	int fpl=strlen(fullpath); // Full path length
	int ppl=strlen(onion_request_get_path(req)); // Partial, the fullpath[fpl-ppl] is the end point of the handler path
	if (strncmp(fullpath, dest, fpl-ppl)!=0){
		char tmp[512];
		int l=fpl-ppl < sizeof(tmp)-1 ? fpl-ppl : sizeof(tmp)-1;
		strncpy(tmp, fullpath, l);
		tmp[l]=0;
		ONION_WARNING("Move to out of this webdav share! (%s is out of %s)", dest, tmp);
		return onion_shortcut_response("Moving out of shared share", HTTP_FORBIDDEN, req, res);
	}
	dest=&dest[fpl-ppl];


	char orig[512];
	snprintf(orig, sizeof(orig), "%s/%s", wd->path, partialpath);
	
	if (wd->check_permissions(wd->path, orig, req)!=0){
		return onion_shortcut_response("Forbidden", HTTP_FORBIDDEN, req, res);
	}

	const char *fdest=filename;

	ONION_INFO("Move %s to %s (webdav)", fullpath, dest_orig);
	
	int ok=onion_shortcut_rename(orig, fdest);

	if (ok==0){
		ONION_DEBUG("Created %s succesfully", fdest);
		return onion_shortcut_response("201 Created", 201, req, res);
	}
	else{
		ONION_ERROR("Could not rename %s to %s (%s)", orig, fdest, strerror(errno));
		return onion_shortcut_response("Could not create resource", HTTP_FORBIDDEN, req, res);
	}
}
Ejemplo n.º 7
0
/**
 * @short Gets the dict with the cookies
 * @memberof onion_request_t
 *
 * @param req Request to get the cookies from
 *
 * @returns A dict with all the cookies. It might be empty.
 *
 * First call it generates the dict.
 */
onion_dict* onion_request_get_cookies_dict(onion_request* req) {
    if (req->cookies)
        return req->cookies;

    req->cookies=onion_dict_new();

    const char *ccookies=onion_request_get_header(req, "Cookie");
    if (!ccookies)
        return req->cookies;
    char *cookies=onion_low_strdup(ccookies); // I prepare a temporary string, will modify it.
    char *val=NULL;
    char *key=NULL;
    char *p=cookies;

    int first=1;
    while(*p) {
        if (*p!=' ' && !key && !val) {
            key=p;
        }
        else if (*p=='=' && key && !val) {
            *p=0;
            val=p+1;
        }
        else if (*p==';' && key && val) {
            *p=0;
            if (first) {
                // The first cookie is special as it is the pointer to the reserved area for all the keys and values
                // for all th eother cookies, to free at dict free.
                onion_dict_add(req->cookies, cookies, val, OD_FREE_KEY);
                first=0;
            }
            else
                onion_dict_add(req->cookies, key, val, 0); /// Can use as static data as will be freed at first cookie free
            ONION_DEBUG0("Add cookie <%s>=<%s> %d", key, val, first);
            val=NULL;
            key=NULL;
        }
        p++;
    }
    if (key && val && val<p) { // A final element, with value.
        if (first)
            onion_dict_add(req->cookies, cookies, val, OD_FREE_KEY);
        else
            onion_dict_add(req->cookies, key, val, 0);
        ONION_DEBUG0("Add cookie <%s>=<%s> %d", key, val, first);
    }

    return req->cookies;
}
Ejemplo n.º 8
0
onion_connection_status post_json_check(json_response *post, onion_request *req, onion_response *res){
	post->processed=1;
	
	FAIL_IF_NOT_EQUAL_INT(onion_request_get_flags(req)&OR_METHODS, OR_POST);
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req, "Content-Type"),"application/json");
	const onion_block *data=onion_request_get_data(req);
	FAIL_IF_EQUAL(data, NULL);
	if (!data)
		return OCS_INTERNAL_ERROR;
	
	FAIL_IF_NOT_EQUAL_INT(onion_block_size(data), sizeof(JSON_EXAMPLE));
	FAIL_IF_NOT_EQUAL_INT(memcmp(onion_block_data(data), JSON_EXAMPLE, sizeof(JSON_EXAMPLE)), 0);
	
	post->processed=2;
	
	return OCS_PROCESSED;
}
Ejemplo n.º 9
0
void t04_create_add_free_GET(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok;
	
	req=onion_request_new(custom_io);
	FAIL_IF_EQUAL(req,NULL);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);
	
	const char *query="GET /myurl%20/is/very/deeply/nested?test=test&query2=query%202&more_query=%20more%20query+10 HTTP/1.0\n"
													"Host: 127.0.0.1\n\r"
													"Other-Header: My header is very long and with spaces...\r\n\r\n";
	
	int i; // Straight write, with clean (keep alive like)
	for (i=0;i<10;i++){
		FAIL_IF_NOT_EQUAL_INT(req->flags,0);
		ok=REQ_WRITE(req, query);
		FAIL_IF_NOT_EQUAL_INT(ok, OCS_CLOSE_CONNECTION);
		FAIL_IF_EQUAL(req->flags,OR_GET|OR_HTTP11);
		
		FAIL_IF_EQUAL(req->headers, NULL);
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->headers,"Host"), "127.0.0.1");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->headers,"Other-Header"), "My header is very long and with spaces...");
    FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->headers,"other-heaDER"), "My header is very long and with spaces...");
    FAIL_IF_NOT_EQUAL_STR( onion_request_get_header(req,"other-heaDER"), "My header is very long and with spaces...");

		FAIL_IF_NOT_EQUAL_STR(req->fullpath,"/myurl /is/very/deeply/nested");
		FAIL_IF_NOT_EQUAL_STR(req->path,"myurl /is/very/deeply/nested");

		FAIL_IF_EQUAL(req->GET,NULL);
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->GET,"test"), "test");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->GET,"query2"), "query 2");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->GET,"more_query"), " more query 10");
		
		onion_request_clean(req);
		FAIL_IF_NOT_EQUAL(req->GET,NULL);
	}
	
	onion_request_free(req);
	
	END_LOCAL();
}
Ejemplo n.º 10
0
/**
 * @short Gets the dict with the cookies
 * @memberof onion_request_t
 * 
 * @param req Request to get the cookies from
 * 
 * @returns A dict with all the cookies. It might be empty.
 * 
 * First call it generates the dict.
 */
onion_dict* onion_request_get_cookies_dict(onion_request* req){
	if (req->cookies)
		return req->cookies;
	
	req->cookies=onion_dict_new();
	
	const char *ccookies=onion_request_get_header(req, "Cookie");
	if (!ccookies)
		return req->cookies;
	char *cookies=strdup(ccookies); // I prepare a temporal string, will modify it.
	char *val=NULL;
	char *key=NULL;
	char *p=cookies;
	
	int dflags=OD_FREE_KEY;
	while(*p){
		if (*p!=' ' && !key && !val){
			key=p;
		}
		else if (*p=='=' && key && !val){
			*p=0;
			val=p+1;
		}
		else if (*p==';' && key && val){
			*p=0;
			onion_dict_add(req->cookies, key, val, dflags); // I duplicate all as will free cookies string later. 
			ONION_DEBUG0("Add cookie <%s>=<%s> %X", key, val, dflags);
			dflags=0; // On the first element, remove all data as is in key.
			val=NULL;
			key=NULL;
		}
		p++;
	}
	if (key && val && val<p){ // A final element, with value.
		onion_dict_add(req->cookies, key, val, dflags);
		ONION_DEBUG0("Add cookie <%s>=<%s> %X", key, val, dflags);
	}
	
	return req->cookies;
}
Ejemplo n.º 11
0
/**
 * @short Handles a propfind
 * 
 * @param path the shared path.
 */
onion_connection_status onion_webdav_propfind(const char *filename, onion_webdav *wd, onion_request* req, onion_response* res){
	ONION_DEBUG0("PROPFIND");
	int depth;
	{
		const char *depths=onion_request_get_header(req, "Depth");
		if (!depths){
			ONION_ERROR("Missing Depth header on webdav request");
			return OCS_INTERNAL_ERROR;
		}
		if (strcmp(depths,"infinity")==0){
			ONION_ERROR("Infinity depth not supported yet.");
			return OCS_INTERNAL_ERROR;
		}
		depth=atoi(depths);
	}

	int props=onion_webdav_parse_propfind(onion_request_get_data(req));
	ONION_DEBUG("Asking for props %08X, depth %d", props, depth);
	
	onion_block *block=onion_webdav_write_propfind(filename, onion_request_get_path(req), depth, props);
	
	if (!block) // No block, resource does not exist
		return onion_shortcut_response("Not found", HTTP_NOT_FOUND, req, res);
		
	ONION_DEBUG0("Printing block %s", onion_block_data(block));
	
	onion_response_set_header(res, "Content-Type", "text/xml; charset=\"utf-8\"");
	onion_response_set_length(res, onion_block_size(block));
	onion_response_set_code(res, HTTP_MULTI_STATUS);
	
	onion_response_write(res, onion_block_data(block), onion_block_size(block));
	
	onion_block_free(block);
	
	return OCS_PROCESSED;
}
Ejemplo n.º 12
0
/**
 * @short This shortcut returns the given file contents. 
 * 
 * It sets all the compilant headers (TODO), cache and so on.
 * 
 * This is the recomended way to send static files; it even can use sendfile Linux call 
 * if suitable (TODO).
 * 
 * It does no security checks, so caller must be security aware.
 */
onion_connection_status onion_shortcut_response_file(const char *filename, onion_request *request, onion_response *res){
	int fd=open(filename,O_RDONLY|O_CLOEXEC);
	
	if (fd<0)
		return OCS_NOT_PROCESSED;

	if(O_CLOEXEC == 0) { // Good compiler know how to cut this out
		int flags=fcntl(fd, F_GETFD);
		if (flags==-1){
			ONION_ERROR("Retrieving flags from file descriptor");
		}
		flags|=FD_CLOEXEC;
		if (fcntl(fd, F_SETFD, flags)==-1){
			ONION_ERROR("Setting O_CLOEXEC to file descriptor");
		}
	}
	
	struct stat st;
	if (stat(filename, &st)!=0){
		ONION_WARNING("File does not exist: %s",filename);
		close(fd);
		return OCS_NOT_PROCESSED;
	}
	
	if (S_ISDIR(st.st_mode)){
		close(fd);
		return OCS_NOT_PROCESSED;
	}
	
	size_t length=st.st_size;
	
	char etag[64];
	onion_shortcut_etag(&st, etag);
		
	const char *range=onion_request_get_header(request, "Range");
	if (range){
		strncat(etag,range,sizeof(etag)-1);
	}
	onion_response_set_header(res, "Etag", etag);
	
	if (range && strncmp(range,"bytes=",6)==0){
		onion_response_set_code(res, HTTP_PARTIAL_CONTENT);
		//ONION_DEBUG("Need just a range: %s",range);
		char tmp[1024];
		strncpy(tmp, range+6, 1024);
		char *start=tmp;
		char *end=tmp;
		while (*end!='-' && *end) end++;
		if (*end=='-'){
			*end='\0';
			end++;
			
			//ONION_DEBUG("Start %s, end %s",start,end);
			size_t ends, starts;
			if (*end)
				ends=atol(end);
			else
				ends=length;
			starts=atol(start);
			length=ends-starts+1;
			lseek(fd, starts, SEEK_SET);
			snprintf(tmp,sizeof(tmp),"bytes %d-%d/%d",(unsigned int)starts, (unsigned int)ends, (unsigned int)st.st_size);
			//onion_response_set_header(res, "Accept-Ranges","bytes");
			onion_response_set_header(res, "Content-Range",tmp);
		}
	}
	
	onion_response_set_length(res, length);
	onion_response_set_header(res, "Content-Type", onion_mime_get(filename) );
	ONION_DEBUG("Mime type is %s",onion_mime_get(filename));

  ONION_DEBUG0("Etag %s", etag);
  const char *prev_etag=onion_request_get_header(request, "If-None-Match");
  if (prev_etag && (strcmp(prev_etag, etag)==0)){
    ONION_DEBUG0("Not modified");
    onion_response_set_length(res, 0);
    onion_response_set_code(res, HTTP_NOT_MODIFIED);
    onion_response_write_headers(res);
    close(fd);
    return OCS_PROCESSED;
  }
	onion_response_write_headers(res);
	if ((onion_request_get_flags(request)&OR_HEAD) == OR_HEAD){ // Just head.
		length=0;
	}
	
	if (length){
#ifdef USE_SENDFILE
		if (request->connection.listen_point->write==(void*)onion_http_write){ // Lets have a house party! I can use sendfile!
			onion_response_write(res,NULL,0);
			ONION_DEBUG("Using sendfile");
			int r=sendfile(request->connection.fd, fd, NULL, length);
			ONION_DEBUG("Wrote %d, should be %d (%s)", r, length, r==length ? "ok" : "nok");
			if (r!=length || r<0){
				ONION_ERROR("Could not send all file (%s)", strerror(errno));
				close(fd);
				return OCS_INTERNAL_ERROR;
			}
			res->sent_bytes+=length;
			res->sent_bytes_total+=length;
		}
		else
#endif
		{ // Ok, no I cant, do it as always.
			int r=0,w;
			size_t tr=0;
			char tmp[4046];
			if (length>sizeof(tmp)){
				size_t max=length-sizeof(tmp);
				while( tr<max ){
					r=read(fd,tmp,sizeof(tmp));
					tr+=r;
					if (r<0)
						break;
					w=onion_response_write(res, tmp, r);
					if (w!=r){
						ONION_ERROR("Wrote less than read: write %d, read %d. Quite probably closed connection.",w,r);
						break;
					}
				}
			}
			if (sizeof(tmp) >= (length-tr)){
				r=read(fd, tmp, length-tr);
				w=onion_response_write(res, tmp, r);
				if (w!=r){
					ONION_ERROR("Wrote less than read: write %d, read %d. Quite probably closed connection.",w,r);
				}
			}
		}
	}
	close(fd);
	return OCS_PROCESSED;
}
Ejemplo n.º 13
0
static int api_mail_do_post(ONION_FUNC_PROTO_STR, int mode)
{
	const char * userid = onion_request_get_query(req, "userid");
	const char * appkey = onion_request_get_query(req, "appkey");
	const char * sessid = onion_request_get_query(req, "sessid");
	const char * token = onion_request_get_query(req, "token");
	const char * to_userid = onion_request_get_query(req, "to_userid");
	const char * title = onion_request_get_query(req, "title");
	const char * backup = onion_request_get_query(req, "backup");

	if(!userid || !appkey || !sessid || !title || !to_userid || !token)
		return api_error(p, req, res, API_RT_WRONGPARAM);

	struct userec *ue = getuser(userid);
	if(!ue)
		return api_error(p, req, res, API_RT_NOSUCHUSER);

	struct userec currentuser;
	memcpy(&currentuser, ue, sizeof(currentuser));
	free(ue);

	int r = check_user_session(&currentuser, sessid, appkey);
	if(r != API_RT_SUCCESSFUL) {
		return api_error(p, req, res, r);
	}

	if(HAS_PERM(PERM_DENYMAIL)) {
		return api_error(p, req, res, API_RT_MAILNOPPERM);
	}

	int uent_index = get_user_utmp_index(sessid);
	struct user_info *ui = &(shm_utmp->uinfo[uent_index]);
	if(strcmp(ui->token, token) != 0) {
		return api_error(p, req, res, API_RT_WRONGTOKEN);
	}

	// 更新 token 和来源 IP
	getrandomstr_r(ui->token, TOKENLENGTH+1);
	const char * fromhost = onion_request_get_header(req, "X-Real-IP");
	memset(ui->from, 0, 20);
	strncpy(ui->from, fromhost, 20);

	if(check_user_maxmail(currentuser)) {
		return api_error(p, req, res, API_RT_MAILFULL);
	}

	struct userec *to_user = getuser(to_userid);
	if(!to_user) {
		return api_error(p, req, res, API_RT_NOSUCHUSER);
	}

	if(inoverride(currentuser.userid, to_user->userid, "rejects")) {
		free(to_user);
		return api_error(p, req, res, API_RT_INUSERBLIST);
	}

	const char * data = onion_request_get_post(req, "content");

	char filename[80];
	sprintf(filename, "bbstmpfs/tmp/%s_%s.tmp", currentuser.userid, ui->token);

	char * data2 = strdup(data);
	while(strstr(data2, "[ESC]") != NULL)
		data2 = string_replace(data2, "[ESC]", "\033");

	char * data_gbk = (char *)malloc(strlen(data2)*2);
	u2g(data2, strlen(data2), data_gbk, strlen(data2)*2);

	f_write(filename, data_gbk);
	free(data2);

	int mark=0;		// 文件标记
	//if(insertattachments(filename, data_gbk, currentuser->userid)>0)
		//mark |= FH_ATTACHED;

	free(data_gbk);

	char * title_tmp = (char *)malloc(strlen(title)*2);
	u2g(title, strlen(title), title_tmp, strlen(title)*2);
	char title_gbk[80], title_tmp2[80];
	strncpy(title_gbk, title_tmp[0]==0 ? "No Subject" : title_tmp, 80);
	snprintf(title_tmp2, 80, "{%s} %s", to_user->userid, title);
	free(title_tmp);

	r = do_mail_post(to_user->userid, title, filename, currentuser.userid,
			currentuser.username, fromhost, 0, mark);
	if(backup && strcasecmp(backup, "true")==0) {
		do_mail_post_to_sent_box(currentuser.userid, title_tmp2, filename, currentuser.userid,
			currentuser.username, fromhost, 0, mark);
	}

	unlink(filename);
	free(to_user);

	if(r<0) {
		return api_error(p, req, res, API_RT_MAILINNERR);
	}

	api_set_json_header(res);
	onion_response_printf(res, "{ \"errcode\":0, \"token\":\"%s\" }", ui->token);

	return OCS_PROCESSED;
}