#define _POSIX_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../src/util.h" extern const XDG_Var XDG[]; int verbose = 0; #define SOCK_BACKLOG 10 #define MAX_COOKIE_LENGTH 4096 char cookie_buffer[MAX_COOKIE_LENGTH]; int setup_socket(const char *cookied_socket_path) { /* delete the cookie socket if it was left behind on a previous run */ unlink(cookied_socket_path); int socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); if(socket_fd < 0) { fprintf(stderr, "socket failed (%s)\n", strerror(errno)); return -1; } struct sockaddr_un sa; sa.sun_family = AF_UNIX; strcpy(sa.sun_path, cookied_socket_path); if(bind(socket_fd, (struct sockaddr*)&sa, sizeof(sa)) < 0) { fprintf(stderr, "bind failed (%s)\n", strerror(errno)); return -1; } if(listen(socket_fd, SOCK_BACKLOG) < 0) { fprintf(stderr, "listen failed (%s)\n", strerror(errno)); return -1; } return socket_fd; } const char *whitelist_path = NULL; GPtrArray *whitelisted_hosts = NULL; time_t whitelist_update_time = 0; void whitelist_line_cb(const gchar* line, void *user_data) { (void) user_data; gchar *norm_host; const gchar *p = line; while(isspace(*p)) p++; if(p[0] == '#' || !p[0]) /* ignore comments and blank lines */ return; if(p[0] == '.') norm_host = g_strdup(p); else norm_host = g_strconcat(".", p, NULL); g_ptr_array_add(whitelisted_hosts, g_strchomp(norm_host)); } gboolean load_whitelist(const char *whitelist_path) { if(!file_exists(whitelist_path)) return FALSE; /* check if the whitelist file was updated */ struct stat f; if(stat(whitelist_path, &f) < 0) return FALSE; if(whitelisted_hosts == NULL) whitelisted_hosts = g_ptr_array_new(); if(f.st_mtime > whitelist_update_time) { /* the file was updated, reload the whitelist */ if(verbose) puts("reloading whitelist"); while(whitelisted_hosts->len > 0) { g_free(g_ptr_array_index(whitelisted_hosts, 0)); g_ptr_array_remove_index_fast(whitelisted_hosts, 0); } for_each_line_in_file(whitelist_path, whitelist_line_cb, NULL); whitelist_update_time = f.st_mtime; } return TRUE; } gboolean should_save_cookie(const char *host) { if(!load_whitelist(whitelist_path)) return TRUE; /* some error with the file, assume no whitelist */ /* we normalize the hostname so it has a . in front like the whitelist entries */ gchar *test_host = (host[0] == '.') ? g_strdup(host) : g_strconcat(".", host, NULL); int hl = strlen(test_host); /* test against each entry in the whitelist */ gboolean result = FALSE; guint i; for(i = 0; i < whitelisted_hosts->len; i++) { /* a match means the host ends with (or is equal to) the whitelist entry */ const gchar *entry = g_ptr_array_index(whitelisted_hosts, i); int el = strlen(entry); result = (el <= hl) && !strcmp(test_host + (hl - el), entry); if(result) break; } g_free(test_host); return result; } void handle_request(SoupCookieJar *j, const char *buff, int len, int fd) { const char *command = buff; const char *scheme = command + strlen(command) + 1; if((scheme - buff) > len) { fprintf(stderr, "got malformed or partial request\n"); return; } const char *host = scheme + strlen(scheme) + 1; if((host - buff) > len) { fprintf(stderr, "got malformed or partial request\n"); return; } const char *path = host + strlen(host) + 1; if((path - buff) > len) { fprintf(stderr, "got malformed or partial request\n"); return; } /* glue the parts back together into a SoupURI */ char *u = g_strconcat(scheme, "://", host, path, NULL); if(verbose) printf("%s %s\n", command, u); SoupURI *uri = soup_uri_new(u); g_free(u); if(!strcmp(command, "GET")) { char *result = soup_cookie_jar_get_cookies(j, uri, TRUE); if(result) { if(verbose) puts(result); if(write(fd, result, strlen(result)+1) < 0) fprintf(stderr, "write failed (%s)", strerror(errno)); g_free(result); } else { if(verbose) puts("-"); if(write(fd, "", 1) < 0) fprintf(stderr, "write failed (%s)", strerror(errno)); } } else if(!strcmp(command, "PUT")) { const char *name_and_val = path + strlen(path) + 1; if((name_and_val - buff) > len) { fprintf(stderr, "got malformed or partial request\n"); return; } if(verbose) puts(name_and_val); if(should_save_cookie(host)) { char *eql = strchr(name_and_val, '='); eql[0] = 0; const char *name = name_and_val; const char *value = eql + 1; SoupCookie *cookie = soup_cookie_new(name, value, host, path, SOUP_COOKIE_MAX_AGE_ONE_YEAR); soup_cookie_jar_add_cookie(j, cookie); } else if(verbose) puts("no, blacklisted."); if(write(fd, "", 1) < 0) fprintf(stderr, "write failed (%s)", strerror(errno)); } soup_uri_free(uri); } void wait_for_things_to_happen_and_then_do_things(SoupCookieJar* j, int cookie_socket) { GArray *connections = g_array_new (FALSE, FALSE, sizeof (int)); while(1) { unsigned int i; int r; fd_set fs; int maxfd = cookie_socket; FD_ZERO(&fs); FD_SET(maxfd, &fs); for(i = 0; i < connections->len; i++) { int fd = g_array_index(connections, int, i); if(fd > maxfd) maxfd = fd; FD_SET(fd, &fs); } r = select(maxfd+1, &fs, NULL, NULL, NULL); if(r < 0) { fprintf(stderr, "select failed (%s)\n", strerror(errno)); continue; } if(FD_ISSET(cookie_socket, &fs)) { /* handle new connection */ int fd = accept(cookie_socket, NULL, NULL); g_array_append_val(connections, fd); if(verbose) puts("got connection."); } for(i = 0; i < connections->len; i++) { /* handle activity on a connection */ int fd = g_array_index(connections, int, i); if(FD_ISSET(fd, &fs)) { r = read(fd, cookie_buffer, MAX_COOKIE_LENGTH); if(r < 0) { fprintf(stderr, "read failed (%s)\n", strerror(errno)); continue; } else if(r == 0) { if(verbose) puts("client hung up."); g_array_remove_index(connections, i); i--; /* other elements in the array are moved down to fill the gap */ continue; } cookie_buffer[r] = 0; handle_request(j, cookie_buffer, r, fd); } } } } void usage(const char *progname) { printf("%s [-s socket-path] [-f cookies.txt] [-w whitelist-file] [-n] [-v]\n", progname); puts("\t-n\tdon't daemonise the process"); puts("\t-v\tbe verbose"); } void daemonise() { int r = fork(); if(r < 0) { fprintf(stderr, "fork failed (%s)", strerror(errno)); exit(1); } else if (r > 0) { /* this is the parent, which has done its job */ exit(0); } if(setsid() < 0) { fprintf(stderr, "setsid failed (%s)", strerror(errno)); exit(1); } } const char *pid_file_path = NULL; const char *cookied_socket_path = NULL; void cleanup_after_signal(int signal) { (void) signal; unlink(pid_file_path); unlink(cookied_socket_path); exit(0); } int main(int argc, char *argv[]) { int i; const char *cookies_txt_path = NULL; gboolean foreground = FALSE; for(i = 1; i < argc && argv[i][0] == '-'; i++) { switch(argv[i][1]) { case 's': cookied_socket_path = argv[++i]; break; case 'f': cookies_txt_path = argv[++i]; break; case 'w': whitelist_path = argv[++i]; break; case 'n': foreground = TRUE; break; case 'v': verbose = 1; break; default: usage(argv[0]); return 1; } } if(!foreground) daemonise(); if(!cookies_txt_path) cookies_txt_path = g_strconcat(get_xdg_var(XDG[1]), "/uzbl/cookies.txt", NULL); if(!cookied_socket_path) cookied_socket_path = g_strconcat(get_xdg_var(XDG[2]), "/uzbl/cookie_daemon_socket", NULL); if(!whitelist_path) whitelist_path = g_strconcat(get_xdg_var(XDG[0]), "/uzbl/cookie_whitelist", NULL); /* write out and lock the pid file. * this ensures that only one uzbl-cookie-manager is running per-socket. * (we should probably also lock the cookies.txt to prevent accidents...) */ pid_file_path = g_strconcat(cookied_socket_path, ".pid", NULL); int lockfd = open(pid_file_path, O_RDWR|O_CREAT, 0600); if(lockfd < 0) { fprintf(stderr, "couldn't open pid file %s (%s)\n", pid_file_path, strerror(errno)); return 1; } if(flock(lockfd, LOCK_EX|LOCK_NB) < 0) { fprintf(stderr, "couldn't lock pid file %s (%s)\n", pid_file_path, strerror(errno)); fprintf(stderr, "uzbl-cookie-manager is probably already running\n"); return 1; } gchar* pids = g_strdup_printf("%d\n", getpid()); write(lockfd, pids, strlen(pids)); g_free(pids); struct sigaction sa; sa.sa_handler = cleanup_after_signal; if(sigaction(SIGINT, &sa, NULL) || sigaction(SIGTERM, &sa, NULL)) { fprintf(stderr, "sigaction failed (%s)\n", strerror(errno)); return 1; } if(!foreground) { /* close STDIO */ close(0); close(1); close(2); } g_type_init(); SoupCookieJar *j = soup_cookie_jar_text_new(cookies_txt_path, FALSE); int cookie_socket = setup_socket(cookied_socket_path); if(cookie_socket < 0) return 1; wait_for_things_to_happen_and_then_do_things(j, cookie_socket); return 0; }