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