1/*
2 * TRLocalPacketFilter.m vi:ts=4:sw=4:expandtab:
3 * Interface to local OpenBSD /dev/pf
4 *
5 * Author: Landon Fuller <landonf@threerings.net>
6 *
7 * Copyright (c) 2006 - 2007 Three Rings Design, Inc.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the copyright holder nor the names of any contributors
19 *    may be used to endorse or promote products derived from this
20 *    software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#import "TRLocalPacketFilter.h"
36
37#ifdef HAVE_PF
38
39#import <fcntl.h>
40#import <unistd.h>
41#import <stdlib.h>
42#import <string.h>
43#import <errno.h>
44#import <assert.h>
45
46#import "TRLog.h"
47#import "xmalloc.h"
48
49/* Private Methods */
50
51@interface TRLocalPacketFilter (Private)
52+ (pferror_t) mapErrno;
53- (int) ioctl: (unsigned long) request withArgp: (void *) argp;
54- (BOOL) pfFromAddress: (TRPFAddress *) source pfaddr: (struct pfr_addr *) dest;
55- (TRPFAddress *) addressFromPF: (struct pfr_addr *) pfaddr;
56@end
57
58
59/**
60 * An interface to a local OpenBSD Packet Filter.
61 */
62@implementation TRLocalPacketFilter
63
64/**
65 * Initialize a new instance.
66 */
67- (id) init {
68    self = [super init];
69    if (self == nil)
70        return self;
71
72    _fd = -1;
73    return self;
74}
75
76
77/**
78 * Open a reference to /dev/pf. Must be called before
79 * any other PF methods.
80 */
81- (pferror_t) open {
82    /* Open a reference to /dev/pf */
83    if ((_fd = open(PF_DEV_PATH, O_RDWR)) == -1)
84        return [TRLocalPacketFilter mapErrno];
85    else
86        return PF_SUCCESS;
87}
88
89
90/**
91 * Close and release any open references to /dev/pf.
92 * This is called automatically when the object is released.
93 */
94- (void) close {
95    if (_fd != -1) {
96        close(_fd);
97        _fd = -1;
98    }
99}
100
101
102- (void) dealloc {
103    [self close];
104    [super dealloc];
105}
106
107
108/** Return an array of table names */
109- (pferror_t) tables: (TRArray **) result {
110    TRArray *tables = nil;
111    struct pfioc_table io;
112    struct pfr_table *table;
113    int size, i;
114
115    /* Initialize the io structure */
116    memset(&io, 0, sizeof(io));
117    io.pfrio_esize = sizeof(struct pfr_table);
118
119    /* First attempt with a reasonable buffer size - 32 tables */
120    size = sizeof(struct pfr_table) * 32;
121    io.pfrio_buffer = xmalloc(size);
122
123    /* Loop until success. */
124    while (1) {
125        io.pfrio_size = size;
126        if ([self ioctl: DIOCRGETTABLES withArgp: &io] == -1) {
127            pferror_t ret;
128
129            ret = [TRLocalPacketFilter mapErrno];
130            free(io.pfrio_buffer);
131            *result = nil;
132            return ret;
133        }
134
135        /* Do we need a larger buffer? */
136        if (io.pfrio_size > size) {
137            /* Allocate the suggested space */
138            size = io.pfrio_size;
139            io.pfrio_buffer = xrealloc(io.pfrio_buffer, size);
140        } else {
141            /* Success! Exit the loop */
142            break;
143        }
144    }
145
146    /* Iterate over the returned tables, building our array */
147    tables = [[TRArray alloc] init];
148
149    size = io.pfrio_size / sizeof(struct pfr_table);
150    table = (struct pfr_table *) io.pfrio_buffer;
151    for (i = 0; i < size; i++) {
152        TRString *name = [[TRString alloc] initWithCString: table->pfrt_name];
153        [tables addObject: name];
154        [name release];
155        table++;
156    }
157
158    free(io.pfrio_buffer);
159    *result = [tables autorelease];
160    return PF_SUCCESS;
161}
162
163
164/**
165 * Clear all addreses from the specified table.
166 */
167- (pferror_t) flushTable: (TRString *) tableName {
168    struct pfioc_table io;
169
170    /* Validate name */
171    if ([tableName length] > sizeof(io.pfrio_table.pfrt_name))
172        return PF_ERROR_INVALID_NAME;
173
174    /* Initialize the io structure */
175    memset(&io, 0, sizeof(io));
176
177    /* Copy in the table name */
178    strcpy(io.pfrio_table.pfrt_name, [tableName cString]);
179
180    /* Issue the ioctl */
181    if ([self ioctl: DIOCRCLRADDRS withArgp: &io] == -1) {
182        return [TRLocalPacketFilter mapErrno];
183    }
184
185    return PF_SUCCESS;
186}
187
188/**
189 * Add an address to the specified table.
190 */
191- (pferror_t) addAddress: (TRPFAddress *) address toTable: (TRString *) tableName {
192    struct pfioc_table io;
193    struct pfr_addr addr;
194
195    /* Validate name */
196    if ([tableName length] > sizeof(io.pfrio_table.pfrt_name))
197        return PF_ERROR_INVALID_NAME;
198
199    /* Initialize the io structure */
200    memset(&io, 0, sizeof(io));
201    io.pfrio_esize = sizeof(struct pfr_addr);
202
203    /* Build the request */
204    strcpy(io.pfrio_table.pfrt_name, [tableName cString]);
205
206    if ([self pfFromAddress: address pfaddr: &addr] != true)
207        return PF_ERROR_INTERNAL;
208    io.pfrio_buffer = &addr;
209
210    io.pfrio_size = 1;
211
212    /* Issue the ioctl */
213    if ([self ioctl: DIOCRADDADDRS withArgp: &io] == -1) {
214        return [TRLocalPacketFilter mapErrno];
215    }
216
217    if (io.pfrio_nadd != 1) {
218        return PF_ERROR_INTERNAL;
219    }
220
221    return PF_SUCCESS;
222}
223
224
225/**
226 * Delete an address from the specified table.
227 */
228- (pferror_t) deleteAddress: (TRPFAddress *) address fromTable: (TRString *) tableName {
229    struct pfioc_table io;
230    struct pfr_addr addr;
231
232    /* Validate name */
233    if ([tableName length] > sizeof(io.pfrio_table.pfrt_name))
234        return PF_ERROR_INVALID_NAME;
235
236    /* Initialize the io structure */
237    memset(&io, 0, sizeof(io));
238    io.pfrio_esize = sizeof(struct pfr_addr);
239
240    /* Build the request */
241    strcpy(io.pfrio_table.pfrt_name, [tableName cString]);
242
243    if ([self pfFromAddress: address pfaddr: &addr] != true)
244        return PF_ERROR_INTERNAL;
245
246    io.pfrio_buffer = &addr;
247    io.pfrio_size = 1;
248
249    /* Issue the ioctl */
250    if ([self ioctl: DIOCRDELADDRS withArgp: &io] == -1) {
251        return [TRLocalPacketFilter mapErrno];
252    }
253
254    if (io.pfrio_ndel != 1) {
255        return PF_ERROR_INTERNAL;
256    }
257
258    return PF_SUCCESS;
259}
260
261
262/**
263 * Return an array of all addresses from the specified table.
264 */
265- (pferror_t) addressesFromTable: (TRString *) tableName withResult: (TRArray **) result {
266    TRArray *addresses = nil;
267    struct pfioc_table io;
268    struct pfr_addr *pfrAddr;
269    int size, i;
270
271    /* Validate name */
272    if ([tableName length] > sizeof(io.pfrio_table.pfrt_name)) {
273        *result = nil;
274        return PF_ERROR_INVALID_NAME;
275    }
276
277    /* Initialize the io structure */
278    memset(&io, 0, sizeof(io));
279    io.pfrio_esize = sizeof(struct pfr_addr);
280
281    /* Copy in the table name */
282    strcpy(io.pfrio_table.pfrt_name, [tableName cString]);
283
284    /* First attempt with a reasonable buffer size - 32 addresses */
285    size = 32;
286    io.pfrio_buffer = xmalloc(size * sizeof(struct pfr_addr));
287
288    /* Loop until success. */
289    while (1) {
290        io.pfrio_size = size;
291        if ([self ioctl: DIOCRGETADDRS withArgp: &io] == -1) {
292            pferror_t ret;
293
294            ret = [TRLocalPacketFilter mapErrno];
295            free(io.pfrio_buffer);
296            *result = nil;
297            return ret;
298        }
299
300        /* Do we need a larger buffer? */
301        if (io.pfrio_size > size) {
302            /* Allocate the suggested space */
303            size = io.pfrio_size;
304            io.pfrio_buffer = xrealloc(io.pfrio_buffer, size * sizeof(struct pfr_addr));
305        } else {
306            /* Success! Exit the loop */
307            break;
308        }
309    }
310
311    /* Iterate over the returned addresses, building our array */
312    addresses = [[TRArray alloc] init];
313
314    pfrAddr = (struct pfr_addr *) io.pfrio_buffer;
315    for (i = 0; i < io.pfrio_size; i++) {
316        TRPFAddress *address = [self addressFromPF: pfrAddr];
317        [addresses addObject: address];
318        [address release];
319        pfrAddr++;
320    }
321
322    free(io.pfrio_buffer);
323    *result = [addresses autorelease];
324    return PF_SUCCESS;
325}
326
327@end
328
329
330/*
331 * TRLocalPacketFilter Private Methods
332 */
333@implementation TRLocalPacketFilter (Private)
334
335/**
336 * Map PF errno values to pferror_t values
337 */
338+ (pferror_t) mapErrno {
339    switch (errno) {
340        case ESRCH:
341            /* Returned when a table, etc, is not found.
342             * "No such process" doesn't make much sense here. */
343            return PF_ERROR_NOT_FOUND;
344
345        case EINVAL:
346            return PF_ERROR_INVALID_ARGUMENT;
347
348        case EPERM:
349            /* Returned when /dev/pf can't be opened, and? */
350            return PF_ERROR_PERMISSION;
351
352        default:
353            return PF_ERROR_UNKNOWN;
354            break;
355    }
356}
357
358/* ioctl() with an extra seat-belt. */
359- (int) ioctl: (unsigned long) request withArgp: (void *) argp {
360    assert(_fd >= 0);
361    return ioctl(_fd, request, argp);
362}
363
364
365/**
366 * Create a new TRPFAddress address with the provided pfr_addr structure.
367 */
368- (TRPFAddress *) addressFromPF: (struct pfr_addr *) pfaddr {
369    TRPortableAddress addr;
370
371    /* Initialize the addr structure */
372    memset(&addr, 0, sizeof(addr));
373    addr.family = pfaddr->pfra_af;
374    addr.netmask = pfaddr->pfra_net;
375
376    switch (addr.family) {
377        case (AF_INET):
378            memcpy(&addr.ip4_addr, &pfaddr->pfra_ip4addr, sizeof(addr.ip4_addr));
379            break;
380        case (AF_INET6):
381            memcpy(&addr.ip6_addr, &pfaddr->pfra_ip6addr, sizeof(addr.ip6_addr));
382            break;
383        default:
384            [TRLog debug: "Unsupported address family: %d", addr.family];
385            return nil;
386    }
387
388    return [[TRPFAddress alloc] initWithPortableAddress: &addr];
389}
390
391
392/**
393 * Copies the address' struct pfr_addr representation
394 * to the provided destination pointer.
395 */
396- (BOOL) pfFromAddress: (TRPFAddress *) source pfaddr: (struct pfr_addr *) dest {
397    TRPortableAddress addr;
398
399    [source address: &addr];
400
401    memset(dest, 0, sizeof(*dest));
402    dest->pfra_af = addr.family;
403    dest->pfra_net = addr.netmask;
404
405    switch (addr.family) {
406        case (AF_INET):
407            memcpy(&dest->pfra_ip4addr, &addr.ip4_addr, sizeof(dest->pfra_ip4addr));
408            return true;
409        case (AF_INET6):
410            memcpy(&dest->pfra_ip6addr, &addr.ip6_addr, sizeof(dest->pfra_ip6addr));
411            return true;
412        default:
413            /* Should be unreachable, as long as we're */
414            [TRLog debug: "Unsupported address family: %d", addr.family];
415            return false;
416    }
417
418    return false;
419}
420
421@end
422
423#endif /* HAVE_PF */
424