We now build libssh2 in Xcode and it's a much better UB/10.4 citizen
[printdrop.git] / Source / AppController.m
1 /*
2 * PrintDrop
3 * Copyright (c) 2008, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "AppController.h"
18 #import <libssh2.h>
19 #import <sys/socket.h>
20 #import <arpa/inet.h>
21 #include <netdb.h>
22
23 @interface AppController (Private)
24
25 - (void)setStatus:(NSString *)msg isError:(BOOL)error;
26
27 - (void)readChannel:(LIBSSH2_CHANNEL *)channel;
28 - (void)uploadAndPrint:(id)sender;
29
30 @end
31
32
33 @implementation AppController
34
35 /**
36 * Set up the printer list
37 */
38 - (id)init
39 {
40 if (self = [super init])
41 {
42 printers = [NSMutableArray array];
43
44 NSArray *values = [NSArray arrayWithObjects:@"Double-Sided, Stapled $0.05", @"publp", nil];
45 NSArray *keys = [NSArray arrayWithObjects:@"displayName", @"unixName", nil];
46 [printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
47
48 values = [NSArray arrayWithObjects:@"Single-Sided, Stapled $0.10", @"pubps", nil];
49 [printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
50
51 values = [NSArray arrayWithObjects:@"Double-Sided, Not Stapled $0.05", @"publpns", nil];
52 [printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
53
54 values = [NSArray arrayWithObjects:@"Single-Sided, Not Stapled $0.10", @"pubpsns", nil];
55 [printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
56 }
57 return self;
58 }
59
60 /**
61 * Sets the status text
62 */
63 - (void)setStatus:(NSString *)msg isError:(BOOL)error
64 {
65 [status setStringValue:msg];
66 if (error)
67 {
68 [status setTextColor:[NSColor redColor]];
69 [progress stopAnimation:self];
70 }
71 else
72 {
73 [status setTextColor:[NSColor blackColor]];
74 }
75 }
76
77 /**
78 * Reads through a channel (in non-blocking) mode until there is no more left to read
79 * and then it returns. This calls sleep(1) so that the channel can have time to process.
80 * Be sure this is threaded otherwis the interface will stall.
81 */
82 - (void)readChannel:(LIBSSH2_CHANNEL *)channel
83 {
84 libssh2_channel_set_blocking(channel, 0);
85
86 char buf[1024];
87 int numbytes;
88 do
89 {
90 memset(&buf, '\0', sizeof(buf));
91 sleep(1);
92 numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
93 } while (numbytes > 0);
94 }
95
96 /**
97 * Sends an item to the printer
98 */
99 - (IBAction)print:(id)sender
100 {
101 [NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
102 }
103
104 /**
105 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
106 * to queue up LPR
107 */
108 - (void)uploadAndPrint:(id)sender
109 {
110 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
111
112 [progress startAnimation:self];
113 [progress setHidden:NO];
114 [status setHidden:NO];
115
116 NSString *printer = [[printersController selection] valueForKey:@"unixName"];
117 FILE *localFile;
118 struct stat fileInfo;
119
120 [self setStatus:@"Connecting to acs.bu.edu" isError:NO];
121 struct sockaddr_in sin;
122 int sock = socket(AF_INET, SOCK_STREAM, 0);
123 sin.sin_port = htons(22);
124 sin.sin_family = AF_INET;
125
126 struct hostent *host = gethostbyname("acs.bu.edu");
127 memcpy(&sin.sin_addr, host->h_addr_list[0], host->h_length);
128
129 if (stat([[dragRegion filePath] UTF8String], &fileInfo))
130 {
131 return [self setStatus:@"Invalid file selected" isError:YES];
132 }
133
134 if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0)
135 {
136 return [self setStatus:@"Could not connect to acs.bu.edu" isError:YES];
137 }
138
139 LIBSSH2_SESSION *ssh = libssh2_session_init();
140 if (ssh == NULL)
141 {
142 return [self setStatus:@"Failed to initialize SSH context" isError:YES];
143 }
144
145 if (libssh2_session_startup(ssh, sock))
146 {
147 return [self setStatus:@"Could not tunnel over SSH" isError:YES];
148 }
149
150 if (libssh2_userauth_password(ssh, [[username stringValue] UTF8String], [[password stringValue] UTF8String]))
151 {
152 [self setStatus:@"Bad username/password" isError:YES];
153 goto shutdown;
154 }
155
156 LIBSSH2_CHANNEL *channel = libssh2_scp_send(ssh, "~/__bu_print_drop__.pdf", 0755, (unsigned long)fileInfo.st_size);
157 if (!channel)
158 {
159 [self setStatus:@"Unable to open upload SCP session" isError:YES];
160 goto shutdown;
161 }
162
163 [self setStatus:@"Uploading file..." isError:NO];
164
165 localFile = fopen([[dragRegion filePath] UTF8String], "r");
166 char buf[1024];
167 char *pbuf;
168 int numread, numwrote;
169 do
170 {
171 numread = fread(buf, 1, sizeof(buf), localFile);
172 if (numread <= 0)
173 {
174 break;
175 }
176
177 pbuf = buf;
178 do
179 {
180 numwrote = libssh2_channel_write(channel, pbuf, numread);
181 pbuf += numwrote;
182 numread -= numread;
183 } while (numwrote > 0);
184
185 } while(1);
186
187 [self setStatus:@"File uploaded!" isError:NO];
188
189 libssh2_channel_send_eof(channel);
190 libssh2_channel_wait_eof(channel);
191 libssh2_channel_wait_closed(channel);
192 libssh2_channel_free(channel);
193 channel = NULL;
194
195 channel = libssh2_channel_open_session(ssh);
196 if (!channel)
197 {
198 [self setStatus:@"Could not open SSH channel for printing" isError:YES];
199 goto shutdown;
200 }
201
202 if (libssh2_channel_request_pty(channel, "vanilla"))
203 {
204 [self setStatus:@"Could not open ANSI TTY" isError:YES];
205 goto shutdown;
206 }
207
208 if (libssh2_channel_shell(channel))
209 {
210 [self setStatus:@"Failed to open remote shell" isError:YES];
211 goto shutdown;
212 }
213
214 [self setStatus:@"Opened remote SSH shell" isError:NO];
215
216 // read the banner
217 [self readChannel:channel];
218
219 // send the job to lpr
220 char *cmd = (char *)[[NSString stringWithFormat:@"touch ~/print-%@.drop\r\n\0", printer] UTF8String];
221 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
222 [self readChannel:channel];
223
224 // remove our temp file
225 cmd = "rm -f __bu_print_drop__.pdf\r\n\0";
226 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
227 [self readChannel:channel];
228
229 [self setStatus:@"Printed!" isError:NO];
230
231 libssh2_channel_send_eof(channel);
232 libssh2_channel_eof(channel);
233 libssh2_channel_close(channel);
234
235 shutdown:
236 if (channel)
237 {
238 //libssh2_channel_free(channel);
239 channel = NULL;
240 }
241 libssh2_session_disconnect(ssh, "Normal disconnect.");
242 libssh2_session_free(ssh);
243
244 close(sock);
245
246 [progress stopAnimation:self];
247
248 [pool release];
249 }
250
251 @end