summaryrefslogtreecommitdiffabout
path: root/pumpkin/PumpKIN.m
Unidiff
Diffstat (limited to 'pumpkin/PumpKIN.m') (more/less context) (ignore whitespace changes)
-rw-r--r--pumpkin/PumpKIN.m301
1 files changed, 301 insertions, 0 deletions
diff --git a/pumpkin/PumpKIN.m b/pumpkin/PumpKIN.m
new file mode 100644
index 0000000..9a4623a
--- a/dev/null
+++ b/pumpkin/PumpKIN.m
@@ -0,0 +1,301 @@
1#include <stdarg.h>
2#include <sys/stat.h>
3
4#import "PumpKIN.h"
5#import "NumberTransformer.h"
6#import "IPTransformer.h"
7#import "XFer.h"
8#import "XFersViewDatasource.h"
9#import "ARequest.h"
10
11
12@implementation PumpKIN
13@synthesize toolbar;
14@synthesize preferencesWindow;
15@synthesize theDefaults;
16
17@synthesize window;
18@synthesize logger;
19@synthesize xfersView;
20
21-(void) updateListener {
22 if(listener) {
23 [listener release]; listener = nil;
24 }
25 if(![[theDefaults.values valueForKey:@"listen"] boolValue]) return;
26 @try {
27 listener = [[DaemonListener listenerWithDefaults] retain];
28 }
29 @catch (NSException *e) {
30 [self log:@"Error starting the server. %@: %@",e.name,e.reason];
31 NSAlert *a = [NSAlert alertWithMessageText:@"Failed to enable tftp server" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Failed to enable tftp server.\n%@\n\n%@",e.name,e.reason];
32 a.alertStyle = NSWarningAlertStyle;
33 enum act_type {
34 actDont = 0,
35 actBindToAny = NSAlertThirdButtonReturn+1,
36 actRemoveTFTPD, actChangePort
37 };
38 id en;
39 if (e.userInfo && (en=(e.userInfo)[@"errno"])) {
40 switch([en intValue]) {
41 case EADDRINUSE:
42 {
43 int p = [[theDefaults.values valueForKey:@"bindPort"] intValue];
44 if(p==69) {
45 a.informativeText = @"The OS reports that the address is already in use.\n\n"
46 "It probably means, that some other programm is listening on the TFTP port."
47 " since Mac OS X comes with its own tftpd, it is the likeliest suspect."
48 " If you're going to use tftp server a lot, you may prefer to use that one."
49 " If you want to use PumpKIN, you can either use unprivileged port (but make sure"
50 " the client is aware of that and supports it) or unload Apple tftpd using"
51 " command 'launchctl remove com.apple.tftpd' (as root). I can try doing either for you.";
52 [a addButtonWithTitle:@"Change port to 6969"].tag = actChangePort;
53 [a addButtonWithTitle:@"Try to stop Apple's tftpd"].tag = actRemoveTFTPD;
54 }else if(p!=6969) {
55 a.informativeText = @"The OS reports that the address is already in use.\n\n"
56 "It probably means, that some other program is listening on the port."
57 " you can either try to find out who's using the port and shut it down or"
58 " change the port. Make sure to notify your peers of the change."
59 " I can help you with changing the port.";
60 [a addButtonWithTitle:@"Change port to 6969"].tag = actChangePort;
61 }else {
62 a.informativeText = @"The OS reports that the address is already in use.\n\n"
63 "It probably means that some other program is listening on the port."
64 "You should either change port to the one that is not used or find the"
65 " offending program and shut it down. Or go on without server.";
66 }
67 }
68 break;
69 case EADDRNOTAVAIL:
70 a.informativeText = @"The OS reports that the address is not available.\n\n"
71 "It probably means, that the IP address you specified is not configured on this machine.\n\n"
72 "You can either ignore the error and go on without TFTP server, fix the ip address, by entering the one"
73 " that is configured, or bind to the special '0.0.0.0' ip address which means listen to any"
74 " address configured. The latter you can do automatically with a single click below.";
75 [a addButtonWithTitle:@"Listen to any address"].tag = actBindToAny;
76 [a addButtonWithTitle:@"Try removing Apple's daemon"].tag = actRemoveTFTPD;
77 break;
78 }
79 };
80 [theDefaults.values setValue:@NO forKey:@"listen"];
81 switch([a runModal]) {
82 case actBindToAny:
83 [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(bindToAny) userInfo:nil repeats:NO];
84 break;
85 case actChangePort:
86 [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(bindTo6969) userInfo:nil repeats:NO];
87 break;
88 case actRemoveTFTPD:
89 {
90 @try {
91 char const *args[] = { 0,"-k",NULL };
92 [self runBiportal:args];
93 }@catch(NSException *e) {
94 [self log:@"Error trying to unload com.apple.tftpd. %@: %@",e.name,e.reason];
95 }
96 }
97 [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(bindAgain) userInfo:nil repeats:NO];
98 break;
99 }
100 }
101}
102
103-(void)bindAgain {
104 [theDefaults.values setValue:@YES forKey:@"listen"];
105}
106-(void)bindTo6969 {
107 [theDefaults.values setValue:@6969 forKey:@"bindPort"];
108 [self bindAgain];
109}
110-(void)bindToAny {
111 [theDefaults.values setValue:@"0.0.0.0" forKey:@"bindAddress"];
112 [self bindAgain];
113}
114
115- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
116 if( object==theDefaults && (
117 [keyPath isEqualToString:@"values.listen"]
118 || [keyPath isEqualToString:@"values.bindPort"]
119 || [keyPath isEqualToString:@"values.bindAddress"]) ) {
120 [self updateListener];
121 }
122}
123
124- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
125{
126 [theDefaults addObserver:self forKeyPath:@"values.listen" options:0 context:0];
127 [theDefaults addObserver:self forKeyPath:@"values.bindAddress" options:0 context:0];
128 [theDefaults addObserver:self forKeyPath:@"values.bindPort" options:0 context:0];
129
130 listener = nil;
131 [window.contentView setWantsLayer:true];
132 window.backgroundColor = [NSColor colorWithPatternImage:[NSImage imageNamed:@"klever-background.png"]];
133 xfersView.dataSource = (xvDatasource = [[XFersViewDatasource alloc] initWithXfers:xfers=[NSMutableArray arrayWithCapacity:4]]);
134 [self updateListener];
135 if(![[theDefaults values] valueForKey:@"tftpRoot"])
136 [self showPreferences:nil];
137}
138
139- (IBAction)showPreferences:(id)sender {
140 [preferencesWindow makeKeyAndOrderFront:preferencesWindow];
141}
142
143- (void)log:(NSString*)fmt,... {
144 va_list vl; va_start(vl, fmt);
145 NSString *s = [[[[NSString alloc] initWithFormat:fmt arguments:vl] autorelease] stringByAppendingString:@"\n"];
146 va_end(vl);
147 NSString *lf = [theDefaults.values valueForKey:@"logFile"];
148 if(lf && ![lf isEqualTo:@""]) {
149 NSFileHandle *l = [NSFileHandle fileHandleForWritingAtPath:lf];
150 if(!l) {
151 [[NSFileManager defaultManager] createFileAtPath:lf contents:nil attributes:nil];
152 l = [NSFileHandle fileHandleForWritingAtPath:lf];
153 }
154 if(!l) {
155 static NSString *bl = nil;
156 if(!(bl && [bl isEqualToString:lf])) {
157 [[logger textStorage] appendAttributedString:[[[NSAttributedString alloc] initWithString:
158 [NSString stringWithFormat:@"Failed to open/create '%@' log file\n",lf] ] autorelease]];
159 if(bl) [bl release];
160 bl = [NSString stringWithString:lf];
161 }
162 }else{
163 [l seekToEndOfFile];
164 [l writeData:[[NSString stringWithFormat:@"[%@] %@",[[NSDate date] description],s] dataUsingEncoding:NSUTF8StringEncoding]];
165 [l closeFile];
166 }
167 }
168 [[logger textStorage] appendAttributedString:[[[NSAttributedString alloc] initWithString:
169 s ] autorelease]];
170 [logger scrollToEndOfDocument:nil];
171}
172
173-(void)registerXfer:(id)xfer {
174 [xfers insertObject:xfer atIndex:0];
175 [self updateXfers];
176}
177-(void)unregisterXfer:(id)xfer {
178 [xfers removeObject:xfer];
179 [self updateXfers];
180}
181-(void)updateXfers {
182 [xfersView reloadData];
183}
184-(BOOL)hasPeer:(struct sockaddr_in*)sin {
185 return NSNotFound!=[xfers indexOfObjectPassingTest:^BOOL(XFer *x,NSUInteger i,BOOL *s) {
186 struct sockaddr_in *p = x.peer;
187 return p->sin_len==sin->sin_len && !memcmp(p, sin, p->sin_len);
188 }];
189}
190
191-(BOOL)hasSelectedXfer {
192 return [xfersView selectedRow]>=0;
193}
194
195-(void)tableViewSelectionDidChange:(NSNotification *)an {
196 [toolbar validateVisibleItems];
197}
198-(BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
199 if([theItem.itemIdentifier isEqualToString:@"abort_xfer"])
200 return self.hasSelectedXfer;
201 return YES;
202}
203-(IBAction)abortXfer:(id)sender {
204 NSInteger r = [xfersView selectedRow];
205 NSAssert(r>=0,@"no selected row");
206 if(r<0) return;
207 XFer *x = xfers[r];
208 [self log:@"Aborting transfer of '%@' %@",x.xferFilename,x.xferPrefix];
209 [x abort];
210}
211
212- (IBAction)getFile:(id)sender {
213 [ARequest getFile];
214}
215- (IBAction)putFile:(id)sender {
216 [ARequest putFile];
217}
218
219- (IBAction)pickTFTPFolder:(id)sender {
220 NSOpenPanel *op = [NSOpenPanel openPanel];
221 op.canChooseDirectories = YES; op.canChooseFiles = NO;
222 op.canCreateDirectories = YES;
223 op.allowsMultipleSelection = NO;
224 op.prompt = @"Set TFTP root";
225 op.title = @"TFTP root";
226 op.nameFieldLabel = @"TFTP root:";
227 if([op runModal]!=NSFileHandlingPanelOKButton) return;
228 [[theDefaults values] setValue:op.URL.path forKey:@"tftpRoot"];
229}
230
231- (IBAction)pickLogFile:(id)sender {
232 NSSavePanel *op = [NSSavePanel savePanel];
233 op.canCreateDirectories = YES;
234 op.prompt = @"Set log file";
235 op.title = @"Log to";
236 op.nameFieldLabel = @"Log to";
237 if([op runModal]!=NSFileHandlingPanelOKButton) return;
238 [[theDefaults values] setValue:op.URL.path forKey:@"logFile"];
239
240}
241
242- (void)runBiportal:(char const**)args {
243 FILE *f=NULL;
244 AuthorizationRef a=nil;
245 @try {
246 NSString *bip=[[NSBundle mainBundle] pathForAuxiliaryExecutable:@"biportal"];
247 struct stat st;
248 if(stat(bip.UTF8String, &st)) [NSException raise:@"ToolFailure" format:@"Can't see my tool"];
249 if(st.st_uid || !(st.st_mode&S_ISUID)) {
250 OSStatus r = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &a);
251 if(r!=errAuthorizationSuccess)
252 [NSException raise:@"AuthFailure" format:@"failed to AuthorizationCreate(): %d",r];
253 AuthorizationItem ai = {kAuthorizationRightExecute,0,NULL,0};
254 AuthorizationRights ar = {1,&ai};
255 r = AuthorizationCopyRights(a, &ar, NULL, kAuthorizationFlagDefaults|kAuthorizationFlagInteractionAllowed|kAuthorizationFlagPreAuthorize|kAuthorizationFlagExtendRights, NULL);
256 if(r!=errAuthorizationSuccess)
257 [NSException raise:@"AuthFailure" format:@"failed to AuthorizationCopyRights(): %d",r];
258 const char *args[] = { NULL };
259#pragma GCC diagnostic push
260#pragma GCC diagnostic ignored "-Wdeprecated"
261 r = AuthorizationExecuteWithPrivileges(a,bip.UTF8String,
262 kAuthorizationFlagDefaults, (char*const*)args, &f);
263#pragma GCC diagnostic pop
264 if(r!=errAuthorizationSuccess)
265 [NSException raise:@"AuthFailure" format:@"failed to AuthorizationExecuteWithPrivileges(): %d",r];
266 int e;
267 int sr = fscanf(f, "%d", &e);
268 fclose(f),f=NULL;
269 if(sr!=1)
270 [NSException raise:@"ToolFailure" format:@"failed to setup tool"];
271 if(e)
272 [NSException raise:@"ToolFailure" format:@"failed to setup tool, error code: %d",e];
273 }
274 *args = bip.UTF8String;
275 pid_t p = fork();
276 if(p<0) [NSException raise:@"ToolFailure" format:@"failed to fork"];
277 if(!p) execv(*args,(char**)args), exit(errno);
278 int r, wp;
279 while((wp=waitpid(p,&r,0))<0 && errno==EINTR);
280 if(wp!=p) [NSException raise:@"ToolFailure" format:@"failed to wait for tool"];
281 if(!WIFEXITED(r)) [NSException raise:@"ToolFailure" format:@"tool failed"];
282 if(WEXITSTATUS(r)) {
283 [[NSException exceptionWithName:@"ToolFailure" reason:[NSString stringWithFormat:@"tool failed, error code: %d", WEXITSTATUS(r)] userInfo:@{@"errno": @WEXITSTATUS(r)}] raise];
284 }
285 }@finally {
286 if(f) fclose(f);
287 if(a) AuthorizationFree(a,kAuthorizationFlagDefaults);
288 }
289}
290
291+(void)initialize {
292 [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:
293 [NSDictionary dictionaryWithContentsOfFile:
294 [[NSBundle mainBundle] pathForResource:@"pumpkin-defaults" ofType:@"plist"]
295 ]
296 ];
297 [NSValueTransformer setValueTransformer:[[[NumberTransformer alloc] init] autorelease] forName:@"PortNumberTransformer"];
298 [NSValueTransformer setValueTransformer:[[[IPTransformer alloc] init] autorelease] forName:@"IPAddressTransformer"];
299}
300
301@end