1 /* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "net.h"
5 #include "ioloop.h"
6 #include "hash.h"
7 #include "strescape.h"
8 #include "login-proxy-state.h"
9 
10 #include <unistd.h>
11 #include <fcntl.h>
12 
13 #define NOTIFY_RETRY_REOPEN_MSECS (60*1000)
14 
15 struct login_proxy_state {
16 	HASH_TABLE(struct login_proxy_record *,
17 		   struct login_proxy_record *) hash;
18 	pool_t pool;
19 
20 	const char *notify_path;
21 	int notify_fd;
22 
23 	struct timeout *to_reopen;
24 };
25 
26 static int login_proxy_state_notify_open(struct login_proxy_state *state);
27 
28 static unsigned int
login_proxy_record_hash(const struct login_proxy_record * rec)29 login_proxy_record_hash(const struct login_proxy_record *rec)
30 {
31 	return net_ip_hash(&rec->ip) ^ rec->port;
32 }
33 
login_proxy_record_cmp(struct login_proxy_record * rec1,struct login_proxy_record * rec2)34 static int login_proxy_record_cmp(struct login_proxy_record *rec1,
35 				  struct login_proxy_record *rec2)
36 {
37 	if (!net_ip_compare(&rec1->ip, &rec2->ip))
38 		return 1;
39 
40 	return (int)rec1->port - (int)rec2->port;
41 }
42 
login_proxy_state_init(const char * notify_path)43 struct login_proxy_state *login_proxy_state_init(const char *notify_path)
44 {
45 	struct login_proxy_state *state;
46 
47 	state = i_new(struct login_proxy_state, 1);
48 	state->pool = pool_alloconly_create("login proxy state", 1024);
49 	hash_table_create(&state->hash, state->pool, 0,
50 			  login_proxy_record_hash, login_proxy_record_cmp);
51 	state->notify_path = p_strdup(state->pool, notify_path);
52 	state->notify_fd = -1;
53 	return state;
54 }
55 
login_proxy_state_close(struct login_proxy_state * state)56 static void login_proxy_state_close(struct login_proxy_state *state)
57 {
58 	i_close_fd_path(&state->notify_fd, state->notify_path);
59 }
60 
login_proxy_state_deinit(struct login_proxy_state ** _state)61 void login_proxy_state_deinit(struct login_proxy_state **_state)
62 {
63 	struct login_proxy_state *state = *_state;
64 	struct hash_iterate_context *iter;
65 	struct login_proxy_record *rec;
66 
67 	*_state = NULL;
68 
69 	/* sanity check: */
70 	iter = hash_table_iterate_init(state->hash);
71 	while (hash_table_iterate(iter, state->hash, &rec, &rec))
72 		i_assert(rec->num_waiting_connections == 0);
73 	hash_table_iterate_deinit(&iter);
74 
75 	timeout_remove(&state->to_reopen);
76 	login_proxy_state_close(state);
77 	hash_table_destroy(&state->hash);
78 	pool_unref(&state->pool);
79 	i_free(state);
80 }
81 
82 struct login_proxy_record *
login_proxy_state_get(struct login_proxy_state * state,const struct ip_addr * ip,in_port_t port)83 login_proxy_state_get(struct login_proxy_state *state,
84 		      const struct ip_addr *ip, in_port_t port)
85 {
86 	struct login_proxy_record *rec, key;
87 
88 	i_zero(&key);
89 	key.ip = *ip;
90 	key.port = port;
91 
92 	rec = hash_table_lookup(state->hash, &key);
93 	if (rec == NULL) {
94 		rec = p_new(state->pool, struct login_proxy_record, 1);
95 		rec->ip = *ip;
96 		rec->port = port;
97 		hash_table_insert(state->hash, rec, rec);
98 	}
99 	return rec;
100 }
101 
login_proxy_state_reopen(struct login_proxy_state * state)102 static void login_proxy_state_reopen(struct login_proxy_state *state)
103 {
104 	timeout_remove(&state->to_reopen);
105 	(void)login_proxy_state_notify_open(state);
106 }
107 
login_proxy_state_notify_open(struct login_proxy_state * state)108 static int login_proxy_state_notify_open(struct login_proxy_state *state)
109 {
110 	if (state->to_reopen != NULL) {
111 		/* reopen later */
112 		return -1;
113 	}
114 
115 	state->notify_fd = open(state->notify_path, O_WRONLY);
116 	if (state->notify_fd == -1) {
117 		i_error("open(%s) failed: %m", state->notify_path);
118 		state->to_reopen = timeout_add(NOTIFY_RETRY_REOPEN_MSECS,
119 					       login_proxy_state_reopen, state);
120 		return -1;
121 	}
122 	fd_set_nonblock(state->notify_fd, TRUE);
123 	return 0;
124 }
125 
login_proxy_state_try_notify(struct login_proxy_state * state,const char * user)126 static bool login_proxy_state_try_notify(struct login_proxy_state *state,
127 					 const char *user)
128 {
129 	size_t len;
130 	ssize_t ret;
131 
132 	if (state->notify_fd == -1) {
133 		if (login_proxy_state_notify_open(state) < 0)
134 			return TRUE;
135 		i_assert(state->notify_fd != -1);
136 	}
137 
138 	T_BEGIN {
139 		const char *cmd;
140 
141 		cmd = t_strconcat(str_tabescape(user), "\n", NULL);
142 		len = strlen(cmd);
143 		ret = write(state->notify_fd, cmd, len);
144 	} T_END;
145 
146 	if (ret != (ssize_t)len) {
147 		if (ret < 0)
148 			i_error("write(%s) failed: %m", state->notify_path);
149 		else {
150 			i_error("write(%s) wrote partial update",
151 				state->notify_path);
152 		}
153 		login_proxy_state_close(state);
154 		/* retry sending */
155 		return FALSE;
156 	}
157 	return TRUE;
158 }
159 
login_proxy_state_notify(struct login_proxy_state * state,const char * user)160 void login_proxy_state_notify(struct login_proxy_state *state,
161 			      const char *user)
162 {
163 	if (!login_proxy_state_try_notify(state, user))
164 		(void)login_proxy_state_try_notify(state, user);
165 }
166