author | Michael Krelin <hacker@klever.net> | 2012-12-08 21:19:17 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2012-12-11 21:59:29 (UTC) |
commit | 8808689fe340bec6e90ab13dd502292b0579cf1f (patch) (unidiff) | |
tree | 45b7c863151341f687b74e40bffcbd7ab5468783 /pumpkin/PumpKIN.m | |
parent | 6e7e413ca364d79673e523c09767c18e7cff1bec (diff) | |
download | pumpkin-osx/0.0.zip pumpkin-osx/0.0.tar.gz pumpkin-osx/0.0.tar.bz2 |
initial osx portosx/0.0
Signed-off-by: Michael Krelin <hacker@klever.net>
-rw-r--r-- | pumpkin/PumpKIN.m | 301 |
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 | ||