1 |
|
|
/* $OpenBSD: authpf.c,v 1.125 2016/03/29 14:53:27 mestre Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (C) 1998 - 2007 Bob Beck (beck@openbsd.org). |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/file.h> |
21 |
|
|
#include <sys/ioctl.h> |
22 |
|
|
#include <sys/socket.h> |
23 |
|
|
#include <sys/stat.h> |
24 |
|
|
#include <sys/time.h> |
25 |
|
|
#include <sys/wait.h> |
26 |
|
|
|
27 |
|
|
#include <netinet/in.h> |
28 |
|
|
#include <arpa/inet.h> |
29 |
|
|
#include <net/if.h> |
30 |
|
|
#include <net/pfvar.h> |
31 |
|
|
|
32 |
|
|
#include <err.h> |
33 |
|
|
#include <errno.h> |
34 |
|
|
#include <login_cap.h> |
35 |
|
|
#include <pwd.h> |
36 |
|
|
#include <grp.h> |
37 |
|
|
#include <signal.h> |
38 |
|
|
#include <stdio.h> |
39 |
|
|
#include <stdlib.h> |
40 |
|
|
#include <string.h> |
41 |
|
|
#include <syslog.h> |
42 |
|
|
#include <unistd.h> |
43 |
|
|
|
44 |
|
|
#include "pathnames.h" |
45 |
|
|
|
46 |
|
|
static int read_config(FILE *); |
47 |
|
|
static void print_message(char *); |
48 |
|
|
static int allowed_luser(struct passwd *); |
49 |
|
|
static int check_luser(char *, char *); |
50 |
|
|
static int remove_stale_rulesets(void); |
51 |
|
|
static int recursive_ruleset_purge(char *, char *); |
52 |
|
|
static int change_filter(int, const char *, const char *); |
53 |
|
|
static int change_table(int, const char *); |
54 |
|
|
static void authpf_kill_states(void); |
55 |
|
|
|
56 |
|
|
int dev; /* pf device */ |
57 |
|
|
char anchorname[PF_ANCHOR_NAME_SIZE] = "authpf"; |
58 |
|
|
char rulesetname[PATH_MAX - PF_ANCHOR_NAME_SIZE - 2]; |
59 |
|
|
char tablename[PF_TABLE_NAME_SIZE] = "authpf_users"; |
60 |
|
|
int user_ip = 1; /* controls whether $user_ip is set */ |
61 |
|
|
|
62 |
|
|
FILE *pidfp; |
63 |
|
|
int pidfd = -1; |
64 |
|
|
char luser[LOGIN_NAME_MAX]; /* username */ |
65 |
|
|
char ipsrc[256]; /* ip as a string */ |
66 |
|
|
char pidfile[PATH_MAX]; /* we save pid in this file. */ |
67 |
|
|
|
68 |
|
|
struct timeval Tstart, Tend; /* start and end times of session */ |
69 |
|
|
|
70 |
|
|
volatile sig_atomic_t want_death; |
71 |
|
|
static void need_death(int signo); |
72 |
|
|
static __dead void do_death(int); |
73 |
|
|
extern char *__progname; /* program name */ |
74 |
|
|
|
75 |
|
|
/* |
76 |
|
|
* User shell for authenticating gateways. Sole purpose is to allow |
77 |
|
|
* a user to ssh to a gateway, and have the gateway modify packet |
78 |
|
|
* filters to allow access, then remove access when the user finishes |
79 |
|
|
* up. Meant to be used only from ssh(1) connections. |
80 |
|
|
*/ |
81 |
|
|
int |
82 |
|
|
main(int argc, char *argv[]) |
83 |
|
|
{ |
84 |
|
|
int lockcnt = 0, n; |
85 |
|
|
FILE *config; |
86 |
|
|
struct in6_addr ina; |
87 |
|
|
struct passwd *pw; |
88 |
|
|
char *cp; |
89 |
|
|
gid_t gid; |
90 |
|
|
uid_t uid; |
91 |
|
|
char *shell; |
92 |
|
|
login_cap_t *lc; |
93 |
|
|
|
94 |
|
|
if (strcmp(__progname, "-authpf-noip") == 0) |
95 |
|
|
user_ip = 0; |
96 |
|
|
|
97 |
|
|
config = fopen(PATH_CONFFILE, "r"); |
98 |
|
|
if (config == NULL) { |
99 |
|
|
syslog(LOG_ERR, "cannot open %s (%m)", PATH_CONFFILE); |
100 |
|
|
exit(1); |
101 |
|
|
} |
102 |
|
|
|
103 |
|
|
if ((cp = getenv("SSH_TTY")) == NULL) { |
104 |
|
|
syslog(LOG_ERR, "non-interactive session connection for authpf"); |
105 |
|
|
exit(1); |
106 |
|
|
} |
107 |
|
|
|
108 |
|
|
if ((cp = getenv("SSH_CLIENT")) == NULL) { |
109 |
|
|
syslog(LOG_ERR, "cannot determine connection source"); |
110 |
|
|
exit(1); |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
if (strlcpy(ipsrc, cp, sizeof(ipsrc)) >= sizeof(ipsrc)) { |
114 |
|
|
syslog(LOG_ERR, "SSH_CLIENT variable too long"); |
115 |
|
|
exit(1); |
116 |
|
|
} |
117 |
|
|
cp = strchr(ipsrc, ' '); |
118 |
|
|
if (!cp) { |
119 |
|
|
syslog(LOG_ERR, "corrupt SSH_CLIENT variable %s", ipsrc); |
120 |
|
|
exit(1); |
121 |
|
|
} |
122 |
|
|
*cp = '\0'; |
123 |
|
|
if (inet_pton(AF_INET, ipsrc, &ina) != 1 && |
124 |
|
|
inet_pton(AF_INET6, ipsrc, &ina) != 1) { |
125 |
|
|
syslog(LOG_ERR, |
126 |
|
|
"cannot determine IP from SSH_CLIENT %s", ipsrc); |
127 |
|
|
exit(1); |
128 |
|
|
} |
129 |
|
|
/* open the pf device */ |
130 |
|
|
dev = open(PATH_DEVFILE, O_RDWR); |
131 |
|
|
if (dev == -1) { |
132 |
|
|
syslog(LOG_ERR, "cannot open packet filter device (%m)"); |
133 |
|
|
goto die; |
134 |
|
|
} |
135 |
|
|
|
136 |
|
|
uid = getuid(); |
137 |
|
|
pw = getpwuid(uid); |
138 |
|
|
if (pw == NULL) { |
139 |
|
|
syslog(LOG_ERR, "cannot find user for uid %u", uid); |
140 |
|
|
goto die; |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
if ((lc = login_getclass(pw->pw_class)) != NULL) |
144 |
|
|
shell = login_getcapstr(lc, "shell", pw->pw_shell, |
145 |
|
|
pw->pw_shell); |
146 |
|
|
else |
147 |
|
|
shell = pw->pw_shell; |
148 |
|
|
|
149 |
|
|
login_close(lc); |
150 |
|
|
|
151 |
|
|
if (strcmp(shell, PATH_AUTHPF_SHELL) && |
152 |
|
|
strcmp(shell, PATH_AUTHPF_SHELL_NOIP)) { |
153 |
|
|
syslog(LOG_ERR, "wrong shell for user %s, uid %u", |
154 |
|
|
pw->pw_name, pw->pw_uid); |
155 |
|
|
if (shell != pw->pw_shell) |
156 |
|
|
free(shell); |
157 |
|
|
goto die; |
158 |
|
|
} |
159 |
|
|
|
160 |
|
|
if (shell != pw->pw_shell) |
161 |
|
|
free(shell); |
162 |
|
|
|
163 |
|
|
/* |
164 |
|
|
* Paranoia, but this data _does_ come from outside authpf, and |
165 |
|
|
* truncation would be bad. |
166 |
|
|
*/ |
167 |
|
|
if (strlcpy(luser, pw->pw_name, sizeof(luser)) >= sizeof(luser)) { |
168 |
|
|
syslog(LOG_ERR, "username too long: %s", pw->pw_name); |
169 |
|
|
goto die; |
170 |
|
|
} |
171 |
|
|
|
172 |
|
|
if ((n = snprintf(rulesetname, sizeof(rulesetname), "%s(%ld)", |
173 |
|
|
luser, (long)getpid())) < 0 || (u_int)n >= sizeof(rulesetname)) { |
174 |
|
|
syslog(LOG_INFO, "%s(%ld) too large, ruleset name will be %ld", |
175 |
|
|
luser, (long)getpid(), (long)getpid()); |
176 |
|
|
if ((n = snprintf(rulesetname, sizeof(rulesetname), "%ld", |
177 |
|
|
(long)getpid())) < 0 || (u_int)n >= sizeof(rulesetname)) { |
178 |
|
|
syslog(LOG_ERR, "pid too large for ruleset name"); |
179 |
|
|
goto die; |
180 |
|
|
} |
181 |
|
|
} |
182 |
|
|
|
183 |
|
|
|
184 |
|
|
/* Make our entry in /var/authpf as ipaddr or username */ |
185 |
|
|
n = snprintf(pidfile, sizeof(pidfile), "%s/%s", |
186 |
|
|
PATH_PIDFILE, user_ip ? ipsrc : luser); |
187 |
|
|
if (n < 0 || (u_int)n >= sizeof(pidfile)) { |
188 |
|
|
syslog(LOG_ERR, "path to pidfile too long"); |
189 |
|
|
goto die; |
190 |
|
|
} |
191 |
|
|
|
192 |
|
|
signal(SIGTERM, need_death); |
193 |
|
|
signal(SIGINT, need_death); |
194 |
|
|
signal(SIGALRM, need_death); |
195 |
|
|
signal(SIGPIPE, need_death); |
196 |
|
|
signal(SIGHUP, need_death); |
197 |
|
|
signal(SIGQUIT, need_death); |
198 |
|
|
signal(SIGTSTP, need_death); |
199 |
|
|
|
200 |
|
|
/* |
201 |
|
|
* If someone else is already using this ip, then this person |
202 |
|
|
* wants to switch users - so kill the old process and exit |
203 |
|
|
* as well. |
204 |
|
|
* |
205 |
|
|
* Note, we could print a message and tell them to log out, but the |
206 |
|
|
* usual case of this is that someone has left themselves logged in, |
207 |
|
|
* with the authenticated connection iconized and someone else walks |
208 |
|
|
* up to use and automatically logs in before using. If this just |
209 |
|
|
* gets rid of the old one silently, the new user never knows they |
210 |
|
|
* could have used someone else's old authentication. If we |
211 |
|
|
* tell them to log out before switching users it is an invitation |
212 |
|
|
* for abuse. |
213 |
|
|
*/ |
214 |
|
|
|
215 |
|
|
do { |
216 |
|
|
int save_errno, otherpid = -1; |
217 |
|
|
char otherluser[LOGIN_NAME_MAX]; |
218 |
|
|
|
219 |
|
|
if ((pidfd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1 || |
220 |
|
|
(pidfp = fdopen(pidfd, "r+")) == NULL) { |
221 |
|
|
if (pidfd != -1) |
222 |
|
|
close(pidfd); |
223 |
|
|
syslog(LOG_ERR, "cannot open or create %s: %s", pidfile, |
224 |
|
|
strerror(errno)); |
225 |
|
|
goto die; |
226 |
|
|
} |
227 |
|
|
|
228 |
|
|
if (flock(fileno(pidfp), LOCK_EX|LOCK_NB) == 0) |
229 |
|
|
break; |
230 |
|
|
save_errno = errno; |
231 |
|
|
|
232 |
|
|
/* Mark our pid, and username to our file. */ |
233 |
|
|
|
234 |
|
|
rewind(pidfp); |
235 |
|
|
/* 31 == MAXLOGNAME - 1 */ |
236 |
|
|
if (fscanf(pidfp, "%d\n%31s\n", &otherpid, otherluser) != 2) |
237 |
|
|
otherpid = -1; |
238 |
|
|
syslog(LOG_DEBUG, "tried to lock %s, in use by pid %d: %s", |
239 |
|
|
pidfile, otherpid, strerror(save_errno)); |
240 |
|
|
|
241 |
|
|
if (otherpid > 0) { |
242 |
|
|
syslog(LOG_INFO, |
243 |
|
|
"killing prior auth (pid %d) of %s by user %s", |
244 |
|
|
otherpid, ipsrc, otherluser); |
245 |
|
|
if (kill((pid_t) otherpid, SIGTERM) == -1) { |
246 |
|
|
syslog(LOG_INFO, |
247 |
|
|
"could not kill process %d: (%m)", |
248 |
|
|
otherpid); |
249 |
|
|
} |
250 |
|
|
} |
251 |
|
|
|
252 |
|
|
/* |
253 |
|
|
* We try to kill the previous process and acquire the lock |
254 |
|
|
* for 10 seconds, trying once a second. if we can't after |
255 |
|
|
* 10 attempts we log an error and give up. |
256 |
|
|
*/ |
257 |
|
|
if (want_death || ++lockcnt > 10) { |
258 |
|
|
if (!want_death) |
259 |
|
|
syslog(LOG_ERR, "cannot kill previous authpf (pid %d)", |
260 |
|
|
otherpid); |
261 |
|
|
fclose(pidfp); |
262 |
|
|
pidfp = NULL; |
263 |
|
|
pidfd = -1; |
264 |
|
|
goto dogdeath; |
265 |
|
|
} |
266 |
|
|
sleep(1); |
267 |
|
|
|
268 |
|
|
/* re-open, and try again. The previous authpf process |
269 |
|
|
* we killed above should unlink the file and release |
270 |
|
|
* it's lock, giving us a chance to get it now |
271 |
|
|
*/ |
272 |
|
|
fclose(pidfp); |
273 |
|
|
pidfp = NULL; |
274 |
|
|
pidfd = -1; |
275 |
|
|
} while (1); |
276 |
|
|
|
277 |
|
|
/* whack the group list */ |
278 |
|
|
gid = getegid(); |
279 |
|
|
if (setgroups(1, &gid) == -1) { |
280 |
|
|
syslog(LOG_INFO, "setgroups: %s", strerror(errno)); |
281 |
|
|
do_death(0); |
282 |
|
|
} |
283 |
|
|
|
284 |
|
|
/* revoke privs */ |
285 |
|
|
uid = getuid(); |
286 |
|
|
if (setresuid(uid, uid, uid) == -1) { |
287 |
|
|
syslog(LOG_INFO, "setresuid: %s", strerror(errno)); |
288 |
|
|
do_death(0); |
289 |
|
|
} |
290 |
|
|
openlog("authpf", LOG_PID | LOG_NDELAY, LOG_DAEMON); |
291 |
|
|
|
292 |
|
|
if (!check_luser(PATH_BAN_DIR, luser) || !allowed_luser(pw)) { |
293 |
|
|
syslog(LOG_INFO, "user %s prohibited", luser); |
294 |
|
|
do_death(0); |
295 |
|
|
} |
296 |
|
|
|
297 |
|
|
if (read_config(config)) { |
298 |
|
|
syslog(LOG_ERR, "invalid config file %s", PATH_CONFFILE); |
299 |
|
|
do_death(0); |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
if (remove_stale_rulesets()) { |
303 |
|
|
syslog(LOG_INFO, "error removing stale rulesets"); |
304 |
|
|
do_death(0); |
305 |
|
|
} |
306 |
|
|
|
307 |
|
|
/* We appear to be making headway, so actually mark our pid */ |
308 |
|
|
rewind(pidfp); |
309 |
|
|
fprintf(pidfp, "%ld\n%s\n", (long)getpid(), luser); |
310 |
|
|
fflush(pidfp); |
311 |
|
|
(void) ftruncate(fileno(pidfp), ftello(pidfp)); |
312 |
|
|
|
313 |
|
|
if (change_filter(1, luser, ipsrc) == -1) { |
314 |
|
|
printf("Unable to modify filters\r\n"); |
315 |
|
|
do_death(0); |
316 |
|
|
} |
317 |
|
|
if (user_ip && change_table(1, ipsrc) == -1) { |
318 |
|
|
printf("Unable to modify table\r\n"); |
319 |
|
|
change_filter(0, luser, ipsrc); |
320 |
|
|
do_death(0); |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
while (1) { |
324 |
|
|
struct stat sb; |
325 |
|
|
char *path_message; |
326 |
|
|
printf("\r\nHello %s. ", luser); |
327 |
|
|
printf("You are authenticated from host \"%s\"\r\n", ipsrc); |
328 |
|
|
setproctitle("%s@%s", luser, ipsrc); |
329 |
|
|
if (asprintf(&path_message, "%s/%s/authpf.message", |
330 |
|
|
PATH_USER_DIR, luser) == -1) |
331 |
|
|
do_death(1); |
332 |
|
|
if (stat(path_message, &sb) == -1 || ! S_ISREG(sb.st_mode)) { |
333 |
|
|
free(path_message); |
334 |
|
|
if ((path_message = strdup(PATH_MESSAGE)) == NULL) |
335 |
|
|
do_death(1); |
336 |
|
|
} |
337 |
|
|
print_message(path_message); |
338 |
|
|
while (1) { |
339 |
|
|
sleep(10); |
340 |
|
|
if (want_death) |
341 |
|
|
do_death(1); |
342 |
|
|
} |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
dogdeath: |
346 |
|
|
printf("\r\n\r\nSorry, this service is currently unavailable due to "); |
347 |
|
|
printf("technical difficulties\r\n\r\n"); |
348 |
|
|
print_message(PATH_PROBLEM); |
349 |
|
|
printf("\r\nYour authentication process (pid %ld) was unable to run\n", |
350 |
|
|
(long)getpid()); |
351 |
|
|
sleep(180); /* them lusers read reaaaaal slow */ |
352 |
|
|
die: |
353 |
|
|
do_death(0); |
354 |
|
|
} |
355 |
|
|
|
356 |
|
|
/* |
357 |
|
|
* reads config file in PATH_CONFFILE to set optional behaviours up |
358 |
|
|
*/ |
359 |
|
|
static int |
360 |
|
|
read_config(FILE *f) |
361 |
|
|
{ |
362 |
|
|
char buf[1024]; |
363 |
|
|
int i = 0; |
364 |
|
|
|
365 |
|
|
do { |
366 |
|
|
char **ap; |
367 |
|
|
char *pair[4], *cp, *tp; |
368 |
|
|
int len; |
369 |
|
|
|
370 |
|
|
if (fgets(buf, sizeof(buf), f) == NULL) { |
371 |
|
|
fclose(f); |
372 |
|
|
return (0); |
373 |
|
|
} |
374 |
|
|
i++; |
375 |
|
|
len = strlen(buf); |
376 |
|
|
if (len == 0) |
377 |
|
|
continue; |
378 |
|
|
if (buf[len - 1] != '\n' && !feof(f)) { |
379 |
|
|
syslog(LOG_ERR, "line %d too long in %s", i, |
380 |
|
|
PATH_CONFFILE); |
381 |
|
|
return (1); |
382 |
|
|
} |
383 |
|
|
buf[len - 1] = '\0'; |
384 |
|
|
|
385 |
|
|
for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) |
386 |
|
|
; /* nothing */ |
387 |
|
|
|
388 |
|
|
if (!*cp || *cp == '#' || *cp == '\n') |
389 |
|
|
continue; |
390 |
|
|
|
391 |
|
|
for (ap = pair; ap < &pair[3] && |
392 |
|
|
(*ap = strsep(&cp, "=")) != NULL; ) { |
393 |
|
|
if (**ap != '\0') |
394 |
|
|
ap++; |
395 |
|
|
} |
396 |
|
|
if (ap != &pair[2]) |
397 |
|
|
goto parse_error; |
398 |
|
|
|
399 |
|
|
tp = pair[1] + strlen(pair[1]); |
400 |
|
|
while ((*tp == ' ' || *tp == '\t') && tp >= pair[1]) |
401 |
|
|
*tp-- = '\0'; |
402 |
|
|
|
403 |
|
|
if (strcasecmp(pair[0], "anchor") == 0) { |
404 |
|
|
if (!pair[1][0] || strlcpy(anchorname, pair[1], |
405 |
|
|
sizeof(anchorname)) >= sizeof(anchorname)) |
406 |
|
|
goto parse_error; |
407 |
|
|
} |
408 |
|
|
if (strcasecmp(pair[0], "table") == 0) { |
409 |
|
|
if (!pair[1][0] || strlcpy(tablename, pair[1], |
410 |
|
|
sizeof(tablename)) >= sizeof(tablename)) |
411 |
|
|
goto parse_error; |
412 |
|
|
} |
413 |
|
|
} while (!feof(f) && !ferror(f)); |
414 |
|
|
fclose(f); |
415 |
|
|
return (0); |
416 |
|
|
|
417 |
|
|
parse_error: |
418 |
|
|
fclose(f); |
419 |
|
|
syslog(LOG_ERR, "parse error, line %d of %s", i, PATH_CONFFILE); |
420 |
|
|
return (1); |
421 |
|
|
} |
422 |
|
|
|
423 |
|
|
|
424 |
|
|
/* |
425 |
|
|
* splatter a file to stdout - max line length of 1024, |
426 |
|
|
* used for spitting message files at users to tell them |
427 |
|
|
* they've been bad or we're unavailable. |
428 |
|
|
*/ |
429 |
|
|
static void |
430 |
|
|
print_message(char *filename) |
431 |
|
|
{ |
432 |
|
|
char buf[1024]; |
433 |
|
|
FILE *f; |
434 |
|
|
|
435 |
|
|
if ((f = fopen(filename, "r")) == NULL) |
436 |
|
|
return; /* fail silently, we don't care if it isn't there */ |
437 |
|
|
|
438 |
|
|
do { |
439 |
|
|
if (fgets(buf, sizeof(buf), f) == NULL) { |
440 |
|
|
fflush(stdout); |
441 |
|
|
fclose(f); |
442 |
|
|
return; |
443 |
|
|
} |
444 |
|
|
} while (fputs(buf, stdout) != EOF && !feof(f)); |
445 |
|
|
fflush(stdout); |
446 |
|
|
fclose(f); |
447 |
|
|
} |
448 |
|
|
|
449 |
|
|
/* |
450 |
|
|
* allowed_luser checks to see if user "luser" is allowed to |
451 |
|
|
* use this gateway by virtue of being listed in an allowed |
452 |
|
|
* users file, namely /etc/authpf/authpf.allow . |
453 |
|
|
* Users may be listed by <username>, %<group>, or @<login_class>. |
454 |
|
|
* |
455 |
|
|
* If /etc/authpf/authpf.allow does not exist, then we assume that |
456 |
|
|
* all users who are allowed in by sshd(8) are permitted to |
457 |
|
|
* use this gateway. If /etc/authpf/authpf.allow does exist, then a |
458 |
|
|
* user must be listed if the connection is to continue, else |
459 |
|
|
* the session terminates in the same manner as being banned. |
460 |
|
|
*/ |
461 |
|
|
static int |
462 |
|
|
allowed_luser(struct passwd *pw) |
463 |
|
|
{ |
464 |
|
|
char *buf, *lbuf; |
465 |
|
|
int matched; |
466 |
|
|
size_t len; |
467 |
|
|
FILE *f; |
468 |
|
|
|
469 |
|
|
if ((f = fopen(PATH_ALLOWFILE, "r")) == NULL) { |
470 |
|
|
if (errno == ENOENT) { |
471 |
|
|
/* |
472 |
|
|
* allowfile doesn't exist, thus this gateway |
473 |
|
|
* isn't restricted to certain users... |
474 |
|
|
*/ |
475 |
|
|
return (1); |
476 |
|
|
} |
477 |
|
|
|
478 |
|
|
/* |
479 |
|
|
* luser may in fact be allowed, but we can't open |
480 |
|
|
* the file even though it's there. probably a config |
481 |
|
|
* problem. |
482 |
|
|
*/ |
483 |
|
|
syslog(LOG_ERR, "cannot open allowed users file %s (%s)", |
484 |
|
|
PATH_ALLOWFILE, strerror(errno)); |
485 |
|
|
return (0); |
486 |
|
|
} else { |
487 |
|
|
/* |
488 |
|
|
* /etc/authpf/authpf.allow exists, thus we do a linear |
489 |
|
|
* search to see if they are allowed. |
490 |
|
|
* also, if username "*" exists, then this is a |
491 |
|
|
* "public" gateway, such as it is, so let |
492 |
|
|
* everyone use it. |
493 |
|
|
*/ |
494 |
|
|
int gl_init = 0, ngroups = NGROUPS_MAX + 1; |
495 |
|
|
gid_t groups[NGROUPS_MAX + 1]; |
496 |
|
|
|
497 |
|
|
lbuf = NULL; |
498 |
|
|
matched = 0; |
499 |
|
|
|
500 |
|
|
while ((buf = fgetln(f, &len))) { |
501 |
|
|
|
502 |
|
|
if (buf[len - 1] == '\n') |
503 |
|
|
buf[len - 1] = '\0'; |
504 |
|
|
else { |
505 |
|
|
if ((lbuf = malloc(len + 1)) == NULL) |
506 |
|
|
err(1, NULL); |
507 |
|
|
memcpy(lbuf, buf, len); |
508 |
|
|
lbuf[len] = '\0'; |
509 |
|
|
buf = lbuf; |
510 |
|
|
} |
511 |
|
|
|
512 |
|
|
if (buf[0] == '@') { |
513 |
|
|
/* check login class */ |
514 |
|
|
if (strcmp(pw->pw_class, buf + 1) == 0) |
515 |
|
|
matched++; |
516 |
|
|
} else if (buf[0] == '%') { |
517 |
|
|
/* check group membership */ |
518 |
|
|
int cnt; |
519 |
|
|
struct group *group; |
520 |
|
|
|
521 |
|
|
if ((group = getgrnam(buf + 1)) == NULL) { |
522 |
|
|
syslog(LOG_ERR, |
523 |
|
|
"invalid group '%s' in %s (%s)", |
524 |
|
|
buf + 1, PATH_ALLOWFILE, |
525 |
|
|
strerror(errno)); |
526 |
|
|
fclose(f); |
527 |
|
|
return (0); |
528 |
|
|
} |
529 |
|
|
|
530 |
|
|
if (!gl_init) { |
531 |
|
|
(void) getgrouplist(pw->pw_name, |
532 |
|
|
pw->pw_gid, groups, &ngroups); |
533 |
|
|
gl_init++; |
534 |
|
|
} |
535 |
|
|
|
536 |
|
|
for ( cnt = 0; cnt < ngroups; cnt++) { |
537 |
|
|
if (group->gr_gid == groups[cnt]) { |
538 |
|
|
matched++; |
539 |
|
|
break; |
540 |
|
|
} |
541 |
|
|
} |
542 |
|
|
} else { |
543 |
|
|
/* check username and wildcard */ |
544 |
|
|
matched = strcmp(pw->pw_name, buf) == 0 || |
545 |
|
|
strcmp("*", buf) == 0; |
546 |
|
|
} |
547 |
|
|
|
548 |
|
|
free(lbuf); |
549 |
|
|
lbuf = NULL; |
550 |
|
|
|
551 |
|
|
if (matched) { |
552 |
|
|
fclose(f); |
553 |
|
|
return (1); /* matched an allowed user/group */ |
554 |
|
|
} |
555 |
|
|
} |
556 |
|
|
syslog(LOG_INFO, "denied access to %s: not listed in %s", |
557 |
|
|
pw->pw_name, PATH_ALLOWFILE); |
558 |
|
|
|
559 |
|
|
/* reuse buf */ |
560 |
|
|
buf = "\n\nSorry, you are not allowed to use this facility!\n"; |
561 |
|
|
fputs(buf, stdout); |
562 |
|
|
} |
563 |
|
|
fflush(stdout); |
564 |
|
|
fclose(f); |
565 |
|
|
return (0); |
566 |
|
|
} |
567 |
|
|
|
568 |
|
|
/* |
569 |
|
|
* check_luser checks to see if user "luser" has been banned |
570 |
|
|
* from using us by virtue of having an file of the same name |
571 |
|
|
* in the "luserdir" directory. |
572 |
|
|
* |
573 |
|
|
* If the user has been banned, we copy the contents of the file |
574 |
|
|
* to the user's screen. (useful for telling the user what to |
575 |
|
|
* do to get un-banned, or just to tell them they aren't |
576 |
|
|
* going to be un-banned.) |
577 |
|
|
*/ |
578 |
|
|
static int |
579 |
|
|
check_luser(char *luserdir, char *luser) |
580 |
|
|
{ |
581 |
|
|
FILE *f; |
582 |
|
|
int n; |
583 |
|
|
char tmp[PATH_MAX]; |
584 |
|
|
|
585 |
|
|
n = snprintf(tmp, sizeof(tmp), "%s/%s", luserdir, luser); |
586 |
|
|
if (n < 0 || (u_int)n >= sizeof(tmp)) { |
587 |
|
|
syslog(LOG_ERR, "provided banned directory line too long (%s)", |
588 |
|
|
luserdir); |
589 |
|
|
return (0); |
590 |
|
|
} |
591 |
|
|
if ((f = fopen(tmp, "r")) == NULL) { |
592 |
|
|
if (errno == ENOENT) { |
593 |
|
|
/* |
594 |
|
|
* file or dir doesn't exist, so therefore |
595 |
|
|
* this luser isn't banned.. all is well |
596 |
|
|
*/ |
597 |
|
|
return (1); |
598 |
|
|
} else { |
599 |
|
|
/* |
600 |
|
|
* luser may in fact be banned, but we can't open the |
601 |
|
|
* file even though it's there. probably a config |
602 |
|
|
* problem. |
603 |
|
|
*/ |
604 |
|
|
syslog(LOG_ERR, "cannot open banned file %s (%s)", |
605 |
|
|
tmp, strerror(errno)); |
606 |
|
|
return (0); |
607 |
|
|
} |
608 |
|
|
} else { |
609 |
|
|
/* |
610 |
|
|
* luser is banned - spit the file at them to |
611 |
|
|
* tell what they can do and where they can go. |
612 |
|
|
*/ |
613 |
|
|
syslog(LOG_INFO, "denied access to %s: %s exists", |
614 |
|
|
luser, tmp); |
615 |
|
|
|
616 |
|
|
/* reuse tmp */ |
617 |
|
|
strlcpy(tmp, "\n\n-**- Sorry, you have been banned! -**-\n\n", |
618 |
|
|
sizeof(tmp)); |
619 |
|
|
while (fputs(tmp, stdout) != EOF && !feof(f)) { |
620 |
|
|
if (fgets(tmp, sizeof(tmp), f) == NULL) { |
621 |
|
|
fflush(stdout); |
622 |
|
|
fclose(f); |
623 |
|
|
return (0); |
624 |
|
|
} |
625 |
|
|
} |
626 |
|
|
fclose(f); |
627 |
|
|
} |
628 |
|
|
fflush(stdout); |
629 |
|
|
return (0); |
630 |
|
|
} |
631 |
|
|
|
632 |
|
|
/* |
633 |
|
|
* Search for rulesets left by other authpf processes (either because they |
634 |
|
|
* died ungracefully or were terminated) and remove them. |
635 |
|
|
*/ |
636 |
|
|
static int |
637 |
|
|
remove_stale_rulesets(void) |
638 |
|
|
{ |
639 |
|
|
struct pfioc_ruleset prs; |
640 |
|
|
u_int32_t nr; |
641 |
|
|
|
642 |
|
|
memset(&prs, 0, sizeof(prs)); |
643 |
|
|
strlcpy(prs.path, anchorname, sizeof(prs.path)); |
644 |
|
|
if (ioctl(dev, DIOCGETRULESETS, &prs)) { |
645 |
|
|
if (errno == EINVAL) |
646 |
|
|
return (0); |
647 |
|
|
else |
648 |
|
|
return (1); |
649 |
|
|
} |
650 |
|
|
|
651 |
|
|
nr = prs.nr; |
652 |
|
|
while (nr) { |
653 |
|
|
char *s, *t; |
654 |
|
|
pid_t pid; |
655 |
|
|
|
656 |
|
|
prs.nr = nr - 1; |
657 |
|
|
if (ioctl(dev, DIOCGETRULESET, &prs)) |
658 |
|
|
return (1); |
659 |
|
|
errno = 0; |
660 |
|
|
if ((t = strchr(prs.name, '(')) == NULL) |
661 |
|
|
t = prs.name; |
662 |
|
|
else |
663 |
|
|
t++; |
664 |
|
|
pid = strtoul(t, &s, 10); |
665 |
|
|
if (!prs.name[0] || errno || |
666 |
|
|
(*s && (t == prs.name || *s != ')'))) |
667 |
|
|
return (1); |
668 |
|
|
if ((kill(pid, 0) && errno != EPERM) || pid == getpid()) { |
669 |
|
|
if (recursive_ruleset_purge(anchorname, prs.name)) |
670 |
|
|
return (1); |
671 |
|
|
} |
672 |
|
|
nr--; |
673 |
|
|
} |
674 |
|
|
return (0); |
675 |
|
|
} |
676 |
|
|
|
677 |
|
|
static int |
678 |
|
|
recursive_ruleset_purge(char *an, char *rs) |
679 |
|
|
{ |
680 |
|
|
struct pfioc_trans_e *t_e = NULL; |
681 |
|
|
struct pfioc_trans *t = NULL; |
682 |
|
|
struct pfioc_ruleset *prs = NULL; |
683 |
|
|
|
684 |
|
|
/* purge rules */ |
685 |
|
|
errno = 0; |
686 |
|
|
if ((t = calloc(1, sizeof(struct pfioc_trans))) == NULL) |
687 |
|
|
goto no_mem; |
688 |
|
|
if ((t_e = calloc(2, sizeof(struct pfioc_trans_e))) == NULL) |
689 |
|
|
goto no_mem; |
690 |
|
|
t->size = 2; |
691 |
|
|
t->esize = sizeof(struct pfioc_trans_e); |
692 |
|
|
t->array = t_e; |
693 |
|
|
t_e[0].type = PF_TRANS_RULESET; |
694 |
|
|
snprintf(t_e[0].anchor, sizeof(t_e[0].anchor), "%s/%s", an, rs); |
695 |
|
|
t_e[1].type = PF_TRANS_TABLE; |
696 |
|
|
|
697 |
|
|
if ((ioctl(dev, DIOCXBEGIN, t) || |
698 |
|
|
ioctl(dev, DIOCXCOMMIT, t)) && |
699 |
|
|
errno != EINVAL) |
700 |
|
|
goto cleanup; |
701 |
|
|
|
702 |
|
|
/* purge any children */ |
703 |
|
|
if ((prs = calloc(1, sizeof(struct pfioc_ruleset))) == NULL) |
704 |
|
|
goto no_mem; |
705 |
|
|
snprintf(prs->path, sizeof(prs->path), "%s/%s", an, rs); |
706 |
|
|
if (ioctl(dev, DIOCGETRULESETS, prs)) { |
707 |
|
|
if (errno != EINVAL) |
708 |
|
|
goto cleanup; |
709 |
|
|
errno = 0; |
710 |
|
|
} else { |
711 |
|
|
int nr = prs->nr; |
712 |
|
|
|
713 |
|
|
while (nr) { |
714 |
|
|
prs->nr = 0; |
715 |
|
|
if (ioctl(dev, DIOCGETRULESET, prs)) |
716 |
|
|
goto cleanup; |
717 |
|
|
|
718 |
|
|
if (recursive_ruleset_purge(prs->path, prs->name)) |
719 |
|
|
goto cleanup; |
720 |
|
|
nr--; |
721 |
|
|
} |
722 |
|
|
} |
723 |
|
|
|
724 |
|
|
no_mem: |
725 |
|
|
if (errno == ENOMEM) |
726 |
|
|
syslog(LOG_ERR, "calloc failed"); |
727 |
|
|
|
728 |
|
|
cleanup: |
729 |
|
|
free(t); |
730 |
|
|
free(t_e); |
731 |
|
|
free(prs); |
732 |
|
|
return (errno); |
733 |
|
|
} |
734 |
|
|
|
735 |
|
|
/* |
736 |
|
|
* Add/remove filter entries for user "luser" from ip "ipsrc" |
737 |
|
|
*/ |
738 |
|
|
static int |
739 |
|
|
change_filter(int add, const char *luser, const char *ipsrc) |
740 |
|
|
{ |
741 |
|
|
char *fdpath = NULL, *userstr = NULL, *ipstr = NULL; |
742 |
|
|
char *rsn = NULL, *fn = NULL; |
743 |
|
|
pid_t pid; |
744 |
|
|
gid_t gid; |
745 |
|
|
int s; |
746 |
|
|
|
747 |
|
|
if (add) { |
748 |
|
|
struct stat sb; |
749 |
|
|
struct group *grent; |
750 |
|
|
char *pargv[13] = { |
751 |
|
|
"pfctl", "-p", "/dev/pf", "-q", "-a", "anchor/ruleset", |
752 |
|
|
"-D", "user_id=X", "-D", "user_ip=X", "-f", "file", NULL |
753 |
|
|
}; |
754 |
|
|
|
755 |
|
|
if((grent = getgrgid(getgid())) == NULL) { |
756 |
|
|
syslog(LOG_ERR, "Group not found user %s, gid %d", |
757 |
|
|
luser, getgid()); |
758 |
|
|
goto error; |
759 |
|
|
} |
760 |
|
|
|
761 |
|
|
if (luser == NULL || !luser[0] || ipsrc == NULL || !ipsrc[0]) { |
762 |
|
|
syslog(LOG_ERR, "invalid luser/ipsrc"); |
763 |
|
|
goto error; |
764 |
|
|
} |
765 |
|
|
|
766 |
|
|
if (asprintf(&rsn, "%s/%s", anchorname, rulesetname) == -1) |
767 |
|
|
goto no_mem; |
768 |
|
|
if (asprintf(&fdpath, "/dev/fd/%d", dev) == -1) |
769 |
|
|
goto no_mem; |
770 |
|
|
if (asprintf(&ipstr, "user_ip=%s", ipsrc) == -1) |
771 |
|
|
goto no_mem; |
772 |
|
|
if (asprintf(&userstr, "user_id=%s", luser) == -1) |
773 |
|
|
goto no_mem; |
774 |
|
|
if (asprintf(&fn, "%s/%s/authpf.rules", |
775 |
|
|
PATH_USER_DIR, luser) == -1) |
776 |
|
|
goto no_mem; |
777 |
|
|
if (stat(fn, &sb) == -1) { |
778 |
|
|
free(fn); |
779 |
|
|
if(asprintf(&fn, "%s/%s/authpf.rules", PATH_GROUP_DIR, |
780 |
|
|
grent->gr_name) == -1) |
781 |
|
|
goto no_mem; |
782 |
|
|
if(stat(fn, &sb) == -1) { |
783 |
|
|
free(fn); |
784 |
|
|
if ((fn = strdup(PATH_PFRULES)) == NULL) |
785 |
|
|
goto no_mem; |
786 |
|
|
} |
787 |
|
|
} |
788 |
|
|
pargv[2] = fdpath; |
789 |
|
|
pargv[5] = rsn; |
790 |
|
|
pargv[7] = userstr; |
791 |
|
|
if (user_ip) { |
792 |
|
|
pargv[9] = ipstr; |
793 |
|
|
pargv[11] = fn; |
794 |
|
|
} else { |
795 |
|
|
pargv[8] = "-f"; |
796 |
|
|
pargv[9] = fn; |
797 |
|
|
pargv[10] = NULL; |
798 |
|
|
} |
799 |
|
|
|
800 |
|
|
switch (pid = fork()) { |
801 |
|
|
case -1: |
802 |
|
|
syslog(LOG_ERR, "fork failed"); |
803 |
|
|
goto error; |
804 |
|
|
case 0: |
805 |
|
|
/* revoke group privs before exec */ |
806 |
|
|
gid = getgid(); |
807 |
|
|
if (setresgid(gid, gid, gid) == -1) { |
808 |
|
|
err(1, "setregid"); |
809 |
|
|
} |
810 |
|
|
execvp(PATH_PFCTL, pargv); |
811 |
|
|
warn("exec of %s failed", PATH_PFCTL); |
812 |
|
|
_exit(1); |
813 |
|
|
} |
814 |
|
|
|
815 |
|
|
/* parent */ |
816 |
|
|
waitpid(pid, &s, 0); |
817 |
|
|
if (s != 0) { |
818 |
|
|
syslog(LOG_ERR, "pfctl exited abnormally"); |
819 |
|
|
goto error; |
820 |
|
|
} |
821 |
|
|
|
822 |
|
|
gettimeofday(&Tstart, NULL); |
823 |
|
|
syslog(LOG_INFO, "allowing %s, user %s", ipsrc, luser); |
824 |
|
|
} else { |
825 |
|
|
remove_stale_rulesets(); |
826 |
|
|
|
827 |
|
|
gettimeofday(&Tend, NULL); |
828 |
|
|
syslog(LOG_INFO, "removed %s, user %s - duration %d seconds", |
829 |
|
|
ipsrc, luser, (int)(Tend.tv_sec - Tstart.tv_sec)); |
830 |
|
|
} |
831 |
|
|
return (0); |
832 |
|
|
no_mem: |
833 |
|
|
syslog(LOG_ERR, "malloc failed"); |
834 |
|
|
error: |
835 |
|
|
free(fdpath); |
836 |
|
|
free(rsn); |
837 |
|
|
free(userstr); |
838 |
|
|
free(ipstr); |
839 |
|
|
free(fn); |
840 |
|
|
return (-1); |
841 |
|
|
} |
842 |
|
|
|
843 |
|
|
/* |
844 |
|
|
* Add/remove this IP from the "authpf_users" table. |
845 |
|
|
*/ |
846 |
|
|
static int |
847 |
|
|
change_table(int add, const char *ipsrc) |
848 |
|
|
{ |
849 |
|
|
struct pfioc_table io; |
850 |
|
|
struct pfr_addr addr; |
851 |
|
|
|
852 |
|
|
bzero(&io, sizeof(io)); |
853 |
|
|
strlcpy(io.pfrio_table.pfrt_name, tablename, |
854 |
|
|
sizeof(io.pfrio_table.pfrt_name)); |
855 |
|
|
io.pfrio_buffer = &addr; |
856 |
|
|
io.pfrio_esize = sizeof(addr); |
857 |
|
|
io.pfrio_size = 1; |
858 |
|
|
|
859 |
|
|
bzero(&addr, sizeof(addr)); |
860 |
|
|
if (ipsrc == NULL || !ipsrc[0]) |
861 |
|
|
return (-1); |
862 |
|
|
if (inet_pton(AF_INET, ipsrc, &addr.pfra_ip4addr) == 1) { |
863 |
|
|
addr.pfra_af = AF_INET; |
864 |
|
|
addr.pfra_net = 32; |
865 |
|
|
} else if (inet_pton(AF_INET6, ipsrc, &addr.pfra_ip6addr) == 1) { |
866 |
|
|
addr.pfra_af = AF_INET6; |
867 |
|
|
addr.pfra_net = 128; |
868 |
|
|
} else { |
869 |
|
|
syslog(LOG_ERR, "invalid ipsrc"); |
870 |
|
|
return (-1); |
871 |
|
|
} |
872 |
|
|
|
873 |
|
|
if (ioctl(dev, add ? DIOCRADDADDRS : DIOCRDELADDRS, &io) && |
874 |
|
|
errno != ESRCH) { |
875 |
|
|
syslog(LOG_ERR, "cannot %s %s from table %s: %s", |
876 |
|
|
add ? "add" : "remove", ipsrc, tablename, |
877 |
|
|
strerror(errno)); |
878 |
|
|
return (-1); |
879 |
|
|
} |
880 |
|
|
return (0); |
881 |
|
|
} |
882 |
|
|
|
883 |
|
|
/* |
884 |
|
|
* This is to kill off states that would otherwise be left behind stateful |
885 |
|
|
* rules. This means we don't need to allow in more traffic than we really |
886 |
|
|
* want to, since we don't have to worry about any luser sessions lasting |
887 |
|
|
* longer than their ssh session. This function is based on |
888 |
|
|
* pfctl_kill_states from pfctl. |
889 |
|
|
*/ |
890 |
|
|
static void |
891 |
|
|
authpf_kill_states(void) |
892 |
|
|
{ |
893 |
|
|
struct pfioc_state_kill psk; |
894 |
|
|
struct pf_addr target; |
895 |
|
|
|
896 |
|
|
memset(&psk, 0, sizeof(psk)); |
897 |
|
|
memset(&target, 0, sizeof(target)); |
898 |
|
|
|
899 |
|
|
if (inet_pton(AF_INET, ipsrc, &target.v4) == 1) |
900 |
|
|
psk.psk_af = AF_INET; |
901 |
|
|
else if (inet_pton(AF_INET6, ipsrc, &target.v6) == 1) |
902 |
|
|
psk.psk_af = AF_INET6; |
903 |
|
|
else { |
904 |
|
|
syslog(LOG_ERR, "inet_pton(%s) failed", ipsrc); |
905 |
|
|
return; |
906 |
|
|
} |
907 |
|
|
|
908 |
|
|
/* Kill all states from ipsrc */ |
909 |
|
|
memcpy(&psk.psk_src.addr.v.a.addr, &target, |
910 |
|
|
sizeof(psk.psk_src.addr.v.a.addr)); |
911 |
|
|
memset(&psk.psk_src.addr.v.a.mask, 0xff, |
912 |
|
|
sizeof(psk.psk_src.addr.v.a.mask)); |
913 |
|
|
if (ioctl(dev, DIOCKILLSTATES, &psk)) |
914 |
|
|
syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)"); |
915 |
|
|
|
916 |
|
|
/* Kill all states to ipsrc */ |
917 |
|
|
memset(&psk.psk_src, 0, sizeof(psk.psk_src)); |
918 |
|
|
memcpy(&psk.psk_dst.addr.v.a.addr, &target, |
919 |
|
|
sizeof(psk.psk_dst.addr.v.a.addr)); |
920 |
|
|
memset(&psk.psk_dst.addr.v.a.mask, 0xff, |
921 |
|
|
sizeof(psk.psk_dst.addr.v.a.mask)); |
922 |
|
|
if (ioctl(dev, DIOCKILLSTATES, &psk)) |
923 |
|
|
syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)"); |
924 |
|
|
} |
925 |
|
|
|
926 |
|
|
/* signal handler that makes us go away properly */ |
927 |
|
|
static void |
928 |
|
|
need_death(int signo) |
929 |
|
|
{ |
930 |
|
|
want_death = 1; |
931 |
|
|
} |
932 |
|
|
|
933 |
|
|
/* |
934 |
|
|
* function that removes our stuff when we go away. |
935 |
|
|
*/ |
936 |
|
|
static __dead void |
937 |
|
|
do_death(int active) |
938 |
|
|
{ |
939 |
|
|
int ret = 0; |
940 |
|
|
|
941 |
|
|
if (active) { |
942 |
|
|
change_filter(0, luser, ipsrc); |
943 |
|
|
if (user_ip) { |
944 |
|
|
change_table(0, ipsrc); |
945 |
|
|
authpf_kill_states(); |
946 |
|
|
} |
947 |
|
|
} |
948 |
|
|
if (pidfile[0] && pidfd != -1) |
949 |
|
|
if (unlink(pidfile) == -1) |
950 |
|
|
syslog(LOG_ERR, "cannot unlink %s (%m)", pidfile); |
951 |
|
|
exit(ret); |
952 |
|
|
} |