/* * File: dpi.c * * Copyright (C) 2002-2007 Jorge Arellano Cid * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /* * Dillo plugins (small programs that interact with dillo) * * Dillo plugins are designed to handle: * bookmarks, cookies, FTP, downloads, files, preferences, https, * datauri and a lot of any-to-html filters. */ #include #include #include #include #include #include /* for errno */ #include #include #include #include #include #include #include "../msg.h" #include "../klist.h" #include "IO.h" #include "Url.h" #include "../../dpip/dpip.h" /* This one is tricky, some sources state it should include the byte * for the terminating NULL, and others say it shouldn't. */ # define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) /* Solaris may not have this one... */ #ifndef AF_LOCAL #define AF_LOCAL AF_UNIX #endif typedef struct { int InTag; int Send2EOF; int DataTotalSize; int DataRecvSize; Dstr *Buf; int BufIdx; int TokIdx; int TokSize; int TokIsTag; ChainLink *InfoRecv; int Key; } dpi_conn_t; /* * Local data */ static Klist_t *ValidConns = NULL; /* Active connections list. It holds * pointers to dpi_conn_t structures. */ /* * Initialize local data */ void a_Dpi_init(void) { /* empty */ } /* * Close a FD handling EINTR */ static void Dpi_close_fd(int fd) { int st; do st = close(fd); while (st < 0 && errno == EINTR); } /* * Create a new connection data structure */ static dpi_conn_t *Dpi_conn_new(ChainLink *Info) { dpi_conn_t *conn = dNew0(dpi_conn_t, 1); conn->Buf = dStr_sized_new(8*1024); conn->InfoRecv = Info; conn->Key = a_Klist_insert(&ValidConns, conn); return conn; } /* * Free a connection data structure */ static void Dpi_conn_free(dpi_conn_t *conn) { a_Klist_remove(ValidConns, conn->Key); dStr_free(conn->Buf, 1); dFree(conn); } /* * Check whether a conn is still valid. * Return: 1 if found, 0 otherwise */ static int Dpi_conn_valid(int key) { return (a_Klist_get_data(ValidConns, key)) ? 1 : 0; } /* * Append the new buffer in 'dbuf' to Buf in 'conn' */ static void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf) { if (dbuf->Code == 0 && dbuf->Size > 0) { dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size); } } /* * Split the data stream into tokens. * Here, a token is either: * a) a dpi tag * b) a raw data chunk * * Return Value: 0 upon a new token, -1 on not enough data. * * TODO: define an API and move this function into libDpip.a. */ static int Dpi_get_token(dpi_conn_t *conn) { int i, resp = -1; char *buf = conn->Buf->str; if (conn->BufIdx == conn->Buf->len) { dStr_truncate(conn->Buf, 0); conn->BufIdx = 0; return resp; } if (conn->Send2EOF) { conn->TokIdx = conn->BufIdx; conn->TokSize = conn->Buf->len - conn->BufIdx; conn->BufIdx = conn->Buf->len; return 0; } _MSG("conn->BufIdx = %d; conn->Buf->len = %d\nbuf: [%s]\n", conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx); if (!conn->InTag) { /* search for start of tag */ while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<') ++conn->BufIdx; if (conn->BufIdx < conn->Buf->len) { /* found */ conn->InTag = 1; conn->TokIdx = conn->BufIdx; } else { MSG_ERR("[Dpi_get_token] Can't find token start\n"); } } if (conn->InTag) { /* search for end of tag (EOT=" '>") */ for (i = conn->BufIdx; i < conn->Buf->len; ++i) if (buf[i] == '>' && i >= 2 && buf[i-1] == '\'' && buf[i-2] == ' ') break; conn->BufIdx = i; if (conn->BufIdx < conn->Buf->len) { /* found EOT */ conn->TokIsTag = 1; conn->TokSize = conn->BufIdx - conn->TokIdx + 1; ++conn->BufIdx; conn->InTag = 0; resp = 0; } } return resp; } /* * Parse a dpi tag and take the appropriate actions */ static void Dpi_parse_token(dpi_conn_t *conn) { char *tag, *cmd, *msg, *urlstr; DataBuf *dbuf; char *Tok = conn->Buf->str + conn->TokIdx; if (conn->Send2EOF) { /* we're receiving data chunks from a HTML page */ dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0); a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, "send_page_2eof"); dFree(dbuf); return; } tag = dStrndup(Tok, (size_t)conn->TokSize); _MSG("Dpi_parse_token: {%s}\n", tag); cmd = a_Dpip_get_attr_l(Tok, conn->TokSize, "cmd"); if (strcmp(cmd, "send_status_message") == 0) { msg = a_Dpip_get_attr_l(Tok, conn->TokSize, "msg"); a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); dFree(msg); } else if (strcmp(cmd, "chat") == 0) { msg = a_Dpip_get_attr_l(Tok, conn->TokSize, "msg"); a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); dFree(msg); } else if (strcmp(cmd, "dialog") == 0) { /* For now will send the dpip tag... */ a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd); } else if (strcmp(cmd, "start_send_page") == 0) { conn->Send2EOF = 1; urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, "url"); a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); dFree(urlstr); /* TODO: a_Dpip_get_attr_l(Tok, conn->TokSize, "send_mode") */ } else if (strcmp(cmd, "reload_request") == 0) { urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, "url"); a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); dFree(urlstr); } dFree(cmd); dFree(tag); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* * Get a new data buffer (within a 'dbuf'), save it into local data, * split in tokens and parse the contents. */ static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn) { DataBuf *dbuf = Data1; int key = conn->Key; /* Very useful for debugging: show the data stream as received. */ /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */ if (Op == IORead) { Dpi_append_dbuf(conn, dbuf); /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */ while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) { Dpi_parse_token(conn); } } else if (Op == IOClose) { /* unused */ } } /* * Start dpid. * Return: 0 starting now, 1 Error. */ static int Dpi_start_dpid(void) { pid_t pid; int st_pipe[2], n, ret = 1; char buf[16]; /* create a pipe to track our child's status */ if (pipe(st_pipe)) return 1; pid = fork(); if (pid == 0) { /* This is the child process. Execute the command. */ char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL); Dpi_close_fd(st_pipe[0]); if (execl(path1, "dpid", (char*)NULL) == -1) { dFree(path1); if (execlp("dpid", "dpid", (char*)NULL) == -1) { MSG("Dpi_start_dpid (child): %s\n", dStrerror(errno)); do n = write(st_pipe[1], "ERROR", 5); while (n == -1 && errno == EINTR); Dpi_close_fd(st_pipe[1]); _exit (EXIT_FAILURE); } } } else if (pid < 0) { /* The fork failed. Report failure. */ MSG("Dpi_start_dpid: %s\n", dStrerror(errno)); /* close the unused pipe */ Dpi_close_fd(st_pipe[0]); Dpi_close_fd(st_pipe[1]); } else { /* This is the parent process, check our child status... */ Dpi_close_fd(st_pipe[1]); do n = read(st_pipe[0], buf, 16); while (n == -1 && errno == EINTR); _MSG("Dpi_start_dpid: n = %d\n", n); if (n != 5) { ret = 0; } else { MSG("Dpi_start_dpid: %s\n", dStrerror(errno)); } } return ret; } /* * Read dpid's communication keys from its saved file. * Return value: 1 on success, -1 on error. */ static int Dpi_read_comm_keys(int *port) { FILE *In; char *fname, *rcline = NULL; int ret = -1; fname = dStrconcat(dGethomedir(), "/.dillo/dpid_comm_keys", NULL); if ((In = fopen(fname, "r")) == NULL) { MSG_ERR("[Dpi_read_comm_keys] %s\n", dStrerror(errno)); } else if ((rcline = dGetline(In)) == NULL) { MSG_ERR("[Dpi_read_comm_keys] empty file: %s\n", fname); } else { *port = strtol(rcline, NULL, 10); ret = 1; } dFree(rcline); dFree(fname); return ret; } /* * Make a connection test for a IDS. * Return: 1 OK, -1 Not working. */ static int Dpi_check_dpid_ids() { struct sockaddr_in sin; const socklen_t sin_sz = sizeof(sin); int sock_fd, dpid_port, ret = -1; /* socket connection test */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (Dpi_read_comm_keys(&dpid_port) != -1) { sin.sin_port = htons(dpid_port); if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { MSG("Dpi_check_dpid_ids: sock_fd=%d %s\n", sock_fd, dStrerror(errno)); } else if (connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) { MSG("Dpi_check_dpid_ids: %s\n", dStrerror(errno)); } else { Dpi_close_fd(sock_fd); ret = 1; } } return ret; } /* * Confirm that the dpid is running. If not, start it. * Return: 0 running OK, 1 starting (EAGAIN), 2 Error. */ static int Dpi_check_dpid(int num_tries) { static int starting = 0; int check_st = 1, ret = 2; check_st = Dpi_check_dpid_ids(); MSG("Dpi_check_dpid: check_st=%d\n", check_st); if (check_st == 1) { /* connection test with dpi server passed */ starting = 0; ret = 0; } else { if (!starting) { /* start dpid */ if (Dpi_start_dpid() == 0) { starting = 1; ret = 1; } } else if (++starting < num_tries) { /* starting */ ret = 1; } else { /* we waited too much, report an error... */ starting = 0; } } _MSG("Dpi_check_dpid:: %s\n", (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR")); return ret; } /* * Confirm that the dpid is running. If not, start it. * Return: 0 running OK, 2 Error. */ static int Dpi_blocking_start_dpid(void) { int cst, try = 0, n_tries = 12; /* 3 seconds */ /* test the dpid, and wait a bit for it to start if necessary */ while ((cst = Dpi_check_dpid(n_tries)) == 1) { MSG("Dpi_blocking_start_dpid: try %d\n", ++try); usleep(250000); /* 1/4 sec */ } return cst; } /* * Return the dpi server's port number, or -1 on error. * (A query is sent to dpid and then its answer parsed) * note: as the available servers and/or the dpi socket directory can * change at any time, we'll ask each time. If someday we find * that connecting each time significantly degrades performance, * an optimized approach can be tried. * TODO: here we should use the credentials in ~/.dillo/dpid_comm_keys * (dpid port and password). */ int Dpi_get_server_port(const char *server_name) { int sock_fd, req_sz, rdlen, dpi_port; int st, dpid_port, ret = -1, ok = 0; struct sockaddr_in sin; char buf[128], *cmd, *request, *rply = NULL, *port_str; size_t buflen; socklen_t sin_sz; dReturn_val_if_fail (server_name != NULL, ret); _MSG("Dpi_get_server_port:: server_name = [%s]\n", server_name); /* Read dpid's port from saved file */ if (Dpi_read_comm_keys(&dpid_port) != -1) { ok = 1; } if (ok) { /* Connect a socket with dpid */ sin_sz = sizeof(sin); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = htons(dpid_port); if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 || connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) { MSG("Dpi_get_server_port: %s\n", dStrerror(errno)); ok = 0; } } if (ok) { /* ask dpid to check the dpi and send its port number back */ request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name); _MSG("[%s]\n", request); do { st = write(sock_fd, request, strlen(request)); } while (st < 0 && errno == EINTR); if (st < 0 && errno != EINTR) { MSG("Dpi_get_server_port: %s\n", dStrerror(errno)); ok = 0; } dFree(request); shutdown(sock_fd, 1); /* signals no more writes to dpid */ } if (ok) { /* Get the reply */ buf[0] = '\0'; buflen = sizeof(buf)/sizeof(buf[0]); for (req_sz = 0; (rdlen = read(sock_fd, buf, buflen)) != 0; req_sz += rdlen) { if (rdlen == -1 && errno == EINTR) continue; if (rdlen == -1) { MSG("Dpi_get_server_port: %s\n", dStrerror(errno)); ok = 0; break; } rply = dRealloc(rply, (uint_t)(req_sz + rdlen + 1)); if (req_sz == 0) rply[0] = '\0'; strncat(rply, buf, (size_t)rdlen); } Dpi_close_fd(sock_fd); if (rdlen == 0 && rply) { _MSG("rply = [%s]\n", rply); ok = 1; } } if (ok) { /* Parse reply */ cmd = a_Dpip_get_attr(rply, "cmd"); if (strcmp(cmd, "send_data") == 0) { port_str = a_Dpip_get_attr(rply, "msg"); MSG("Dpi_get_server_port: rply=%s\n", rply); MSG("Dpi_get_server_port: port_str=%s\n", port_str); dpi_port = strtol(port_str, NULL, 10); dFree(port_str); ok = 1; } dFree(cmd); } dFree(rply); return ok ? dpi_port : -1; } /* * Connect a socket to a dpi server and return the socket's FD. * We have to ask 'dpid' (dpi daemon) for the port of the target dpi server. * Once we have it, then the proper file descriptor is returned (-1 on error). */ static int Dpi_connect_socket(const char *server_name, int retry) { struct sockaddr_in sin; int sock_fd, err, dpi_port; /* Query dpid for the port number for this server */ if ((dpi_port = Dpi_get_server_port(server_name)) == -1) { MSG("Dpi_connect_socket:: can't get port number for %s\n", server_name); return -1; } MSG("Dpi_connect_socket: server=%s port=%d\n", server_name, dpi_port); /* connect with this server's socket */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = htons(dpi_port); if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("[dpi::socket]"); } else if (connect(sock_fd, (void*)&sin, sizeof(sin)) == -1) { err = errno; sock_fd = -1; MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno)); if (retry) { switch (err) { case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL: sock_fd = Dpi_connect_socket(server_name, FALSE); break; } } } return sock_fd; } /* * CCC function for the Dpi module */ void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2) { dpi_conn_t *conn; int SockFD = -1, st; dReturn_if_fail( a_Chain_check("a_Dpi_ccc", Op, Branch, Dir, Info) ); if (Branch == 1) { if (Dir == BCK) { /* Send commands to dpi-server */ switch (Op) { case OpStart: if ((st = Dpi_blocking_start_dpid()) == 0) { SockFD = Dpi_connect_socket(Data1, TRUE); if (SockFD != -1) { int *fd = dNew(int, 1); *fd = SockFD; Info->LocalKey = fd; a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1); a_Chain_bcb(OpStart, Info, NULL, NULL); } } if (st == 0 && SockFD != -1) { a_Chain_bcb(OpSend, Info, &SockFD, "FD"); a_Chain_fcb(OpSend, Info, &SockFD, "FD"); a_Chain_fcb(OpSend, Info, NULL, "DpidOK"); } else { MSG_ERR("dpi.c: can't start dpi daemon\n"); a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, "DpidERROR"); } break; case OpSend: a_Chain_bcb(OpSend, Info, Data1, NULL); break; case OpEnd: a_Chain_bcb(OpEnd, Info, NULL, NULL); dFree(Info->LocalKey); dFree(Info); break; case OpAbort: a_Chain_bcb(OpAbort, Info, NULL, NULL); dFree(Info->LocalKey); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } else { /* 1 FWD */ /* Send commands to dpi-server (status) */ switch (Op) { case OpAbort: a_Chain_fcb(OpAbort, Info, NULL, Data2); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } } else if (Branch == 2) { if (Dir == FWD) { /* Receiving from server */ switch (Op) { case OpSend: /* Data1 = dbuf */ Dpi_process_dbuf(IORead, Data1, Info->LocalKey); break; case OpEnd: a_Chain_fcb(OpEnd, Info, NULL, NULL); Dpi_conn_free(Info->LocalKey); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } else { /* 2 BCK */ switch (Op) { case OpStart: conn = Dpi_conn_new(Info); Info->LocalKey = conn; /* Hack: for receiving HTTP through the DPI framework */ if (strcmp(Data2, "http") == 0) { conn->Send2EOF = 1; } a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2); a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */ break; case OpSend: if (Data2 && !strcmp(Data2, "FD")) { a_Chain_bcb(OpSend, Info, Data1, Data2); } break; case OpAbort: a_Chain_bcb(OpAbort, Info, NULL, NULL); Dpi_conn_free(Info->LocalKey); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } } } /*! Let dpid know dillo is no longer running. * Note: currently disabled. It may serve to let the cookies dpi know * when to expire session cookies. */ void a_Dpi_dillo_exit() { } /* * Send a command to a dpi server, and block until the answer is got. * Return value: the dpip tag answer as an string, NULL on error. */ char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd) { int cst, SockFD; ssize_t st; char buf[16384], *retval = NULL; /* test the dpid, and wait a bit for it to start if necessary */ if ((cst = Dpi_blocking_start_dpid()) != 0) { return retval; } SockFD = Dpi_connect_socket(server_name, TRUE); if (SockFD != -1) { /* TODO: handle the case of (st < strlen(cmd)) */ do st = write(SockFD, cmd, strlen(cmd)); while (st == -1 && errno == EINTR); /* TODO: if the answer is too long... */ do st = read(SockFD, buf, 16384); while (st < 0 && errno == EINTR); if (st == -1) perror("[a_Dpi_send_blocking_cmd]"); else if (st > 0) retval = dStrndup(buf, (size_t)st); Dpi_close_fd(SockFD); } else { perror("[a_Dpi_send_blocking_cmd]"); } return retval; }