476780c60dc5e12753796a7492dd88e354c1d731
[citadel.git] / citadel / citadel_ipc.c
1 /* $Id$ */
2
3 #include "sysdep.h"
4 #if TIME_WITH_SYS_TIME
5 # include <sys/time.h>
6 # include <time.h>
7 #else
8 # if HAVE_SYS_TIME_H
9 #  include <sys/time.h>
10 # else
11 #  include <time.h>
12 # endif
13 #endif
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <sys/types.h>
17 #include <string.h>
18 #ifdef HAVE_MALLOC_H
19 #include <malloc.h>
20 #endif
21 #include <stdlib.h>
22 #include <ctype.h>
23 #include <sys/socket.h>
24 #include <arpa/inet.h>
25 #include <netinet/in.h>
26 #include <netdb.h>
27 #include <sys/un.h>
28 #include <errno.h>
29 #ifdef THREADED_CLIENT
30 #include <pthread.h>
31 #endif
32 #include <libcitadel.h>
33 #include "citadel.h"
34 #include "citadel_ipc.h"
35 #include "citadel_decls.h"
36 #include "citadel_dirs.h"
37 #ifdef THREADED_CLIENT
38 pthread_mutex_t rwlock;
39 #endif
40
41 #ifdef HAVE_OPENSSL
42 static SSL_CTX *ssl_ctx;
43 char arg_encrypt;
44 char rc_encrypt;
45 #ifdef THREADED_CLIENT
46 pthread_mutex_t **Critters;                     /* Things that need locking */
47 #endif /* THREADED_CLIENT */
48
49 #endif /* HAVE_OPENSSL */
50
51 #ifndef INADDR_NONE
52 #define INADDR_NONE 0xffffffff
53 #endif
54
55 static void (*status_hook)(char *s) = NULL;
56
57 void setCryptoStatusHook(void (*hook)(char *s)) {
58         status_hook = hook;
59 }
60
61 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC *ipc, void (*hook)(int state)) {
62         ipc->network_status_cb = hook;
63 }
64
65
66 char instant_msgs = 0;
67
68
69 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes);
70 static void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
71 #ifdef HAVE_OPENSSL
72 static void serv_read_ssl(CtdlIPC *ipc, char *buf, unsigned int bytes);
73 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
74 static void endtls(SSL *ssl);
75 #ifdef THREADED_CLIENT
76 static unsigned long id_callback(void);
77 #endif /* THREADED_CLIENT */
78 #endif /* HAVE_OPENSSL */
79 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf);
80 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf);
81
82
83
84 const char *svn_revision(void);
85
86 /*
87  * Does nothing.  The server should always return 200.
88  */
89 int CtdlIPCNoop(CtdlIPC *ipc)
90 {
91         char aaa[128];
92
93         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
94 }
95
96
97 /*
98  * Does nothing interesting.  The server should always return 200
99  * along with your string.
100  */
101 int CtdlIPCEcho(CtdlIPC *ipc, const char *arg, char *cret)
102 {
103         register int ret;
104         char *aaa;
105         
106         if (!arg) return -2;
107         if (!cret) return -2;
108
109         aaa = (char *)malloc((size_t)(strlen(arg) + 6));
110         if (!aaa) return -1;
111
112         sprintf(aaa, "ECHO %s", arg);
113         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
114         free(aaa);
115         return ret;
116 }
117
118
119 /*
120  * Asks the server to close the connecction.
121  * Should always return 200.
122  */
123 int CtdlIPCQuit(CtdlIPC *ipc)
124 {
125         register int ret = 221;         /* Default to successful quit */
126         char aaa[SIZ]; 
127
128         CtdlIPC_lock(ipc);
129         if (ipc->sock > -1) {
130                 CtdlIPC_putline(ipc, "QUIT");
131                 CtdlIPC_getline(ipc, aaa);
132                 ret = atoi(aaa);
133         }
134 #ifdef HAVE_OPENSSL
135         if (ipc->ssl)
136                 SSL_shutdown(ipc->ssl);
137         ipc->ssl = NULL;
138 #endif
139         if (ipc->sock)
140                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
141         ipc->sock = -1;
142         CtdlIPC_unlock(ipc);
143         return ret;
144 }
145
146
147 /*
148  * Asks the server to log out.  Should always return 200, even if no user
149  * was logged in.  The user will not be logged in after this!
150  */
151 int CtdlIPCLogout(CtdlIPC *ipc)
152 {
153         register int ret;
154         char aaa[SIZ];
155
156         CtdlIPC_lock(ipc);
157         CtdlIPC_putline(ipc, "LOUT");
158         CtdlIPC_getline(ipc, aaa);
159         ret = atoi(aaa);
160         CtdlIPC_unlock(ipc);
161         return ret;
162 }
163
164
165 /*
166  * First stage of authentication - pass the username.  Returns 300 if the
167  * username is able to log in, with the username correctly spelled in cret.
168  * Returns various 500 error codes if the user doesn't exist, etc.
169  */
170 int CtdlIPCTryLogin(CtdlIPC *ipc, const char *username, char *cret)
171 {
172         register int ret;
173         char *aaa;
174
175         if (!username) return -2;
176         if (!cret) return -2;
177
178         aaa = (char *)malloc((size_t)(strlen(username) + 6));
179         if (!aaa) return -1;
180
181         sprintf(aaa, "USER %s", username);
182         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
183         free(aaa);
184         return ret;
185 }
186
187
188 /*
189  * Second stage of authentication - provide password.  The server returns
190  * 200 and several arguments in cret relating to the user's account.
191  */
192 int CtdlIPCTryPassword(CtdlIPC *ipc, const char *passwd, char *cret)
193 {
194         register int ret;
195         char *aaa;
196
197         if (!passwd) return -2;
198         if (!cret) return -2;
199
200         aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
201         if (!aaa) return -1;
202
203         sprintf(aaa, "PASS %s", passwd);
204         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
205         free(aaa);
206         return ret;
207 }
208
209
210 /*
211  * Second stage of authentication - provide password.  The server returns
212  * 200 and several arguments in cret relating to the user's account.
213  */
214 int CtdlIPCTryApopPassword(CtdlIPC *ipc, const char *response, char *cret)
215 {
216         register int ret;
217         char *aaa;
218
219         if (!response) return -2;
220         if (!cret) return -2;
221
222         aaa = (char *)malloc((size_t)(strlen(response) + 6));
223         if (!aaa) return -1;
224
225         sprintf(aaa, "PAS2 %s", response);
226         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
227         free(aaa);
228         return ret;
229 }
230
231
232 /*
233  * Create a new user.  This returns 200 plus the same arguments as TryPassword
234  * if selfservice is nonzero, unless there was a problem creating the account.
235  * If selfservice is zero, creates a new user but does not log out the existing
236  * user - intended for use by system administrators to create accounts on
237  * behalf of other users.
238  */
239 int CtdlIPCCreateUser(CtdlIPC *ipc, const char *username, int selfservice, char *cret)
240 {
241         register int ret;
242         char *aaa;
243
244         if (!username) return -2;
245         if (!cret) return -2;
246
247         aaa = (char *)malloc((size_t)(strlen(username) + 6));
248         if (!aaa) return -1;
249
250         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU",  username);
251         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
252         free(aaa);
253         return ret;
254 }
255
256
257 /*
258  * Changes the user's password.  Returns 200 if changed, errors otherwise.
259  */
260 int CtdlIPCChangePassword(CtdlIPC *ipc, const char *passwd, char *cret)
261 {
262         register int ret;
263         char *aaa;
264
265         if (!passwd) return -2;
266         if (!cret) return -2;
267
268         aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
269         if (!aaa) return -1;
270
271         sprintf(aaa, "SETP %s", passwd);
272         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
273         free(aaa);
274         return ret;
275 }
276
277
278 /* LKRN */
279 /* Caller must free the march list */
280 /* Room types are defined in enum RoomList; keep these in sync! */
281 /* floor is -1 for all, or floornum */
282 int CtdlIPCKnownRooms(CtdlIPC *ipc, enum RoomList which, int floor, struct march **listing, char *cret)
283 {
284         register int ret;
285         struct march *march = NULL;
286         static char *proto[] =
287                 {"LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
288         char aaa[SIZ];
289         char *bbb = NULL;
290         size_t bbb_len;
291
292         if (!listing) return -2;
293         if (*listing) return -2;        /* Free the listing first */
294         if (!cret) return -2;
295         /* if (which < 0 || which > 4) return -2; */
296         if (floor < -1) return -2;      /* Can't validate upper bound, sorry */
297
298         sprintf(aaa, "%s %d", proto[which], floor);
299         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
300         if (ret / 100 == 1) {
301                 struct march *mptr;
302
303                 while (bbb && strlen(bbb)) {
304                         int a;
305
306                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
307                         a = strlen(aaa);
308                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
309                         mptr = (struct march *) malloc(sizeof (struct march));
310                         if (mptr) {
311                                 mptr->next = NULL;
312                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
313                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
314                                 mptr->march_floor = (char) extract_int(aaa, 2);
315                                 mptr->march_order = (char) extract_int(aaa, 3);
316                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
317                                 mptr->march_access = (char) extract_int(aaa, 5);
318                                 if (march == NULL)
319                                         march = mptr;
320                                 else {
321                                         struct march *mptr2;
322
323                                         mptr2 = march;
324                                         while (mptr2->next != NULL)
325                                                 mptr2 = mptr2->next;
326                                         mptr2->next = mptr;
327                                 }
328                         }
329                 }
330         }
331         *listing = march;
332         if (bbb) free(bbb);
333         return ret;
334 }
335
336
337 /* GETU */
338 /* Caller must free the struct ctdluser; caller may pass an existing one */
339 int CtdlIPCGetConfig(CtdlIPC *ipc, struct ctdluser **uret, char *cret)
340 {
341         register int ret;
342
343         if (!cret) return -2;
344         if (!uret) return -2;
345         if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof (struct ctdluser));
346         if (!*uret) return -1;
347
348         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
349         if (ret / 100 == 2) {
350                 uret[0]->USscreenwidth = extract_int(cret, 0);
351                 uret[0]->USscreenheight = extract_int(cret, 1);
352                 uret[0]->flags = extract_int(cret, 2);
353         }
354         return ret;
355 }
356
357
358 /* SETU */
359 int CtdlIPCSetConfig(CtdlIPC *ipc, struct ctdluser *uret, char *cret)
360 {
361         char aaa[48];
362
363         if (!uret) return -2;
364         if (!cret) return -2;
365
366         sprintf(aaa, "SETU %d|%d|%d",
367                         uret->USscreenwidth, uret->USscreenheight,
368                         uret->flags);
369         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
370 }
371
372
373 /* RENU */
374 int CtdlIPCRenameUser(CtdlIPC *ipc, char *oldname, char *newname, char *cret)
375 {
376         register int ret;
377         char cmd[256];
378
379         if (!oldname) return -2;
380         if (!newname) return -2;
381         if (!cret) return -2;
382
383         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
384         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
385         return ret;
386 }
387
388
389 /* GOTO */
390 int CtdlIPCGotoRoom(CtdlIPC *ipc, const char *room, const char *passwd,
391                 struct ctdlipcroom **rret, char *cret)
392 {
393         register int ret;
394         char *aaa;
395
396         if (!cret) return -2;
397         if (!rret) return -2;
398         if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
399         if (!*rret) return -1;
400
401         if (passwd) {
402                 aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
403                 if (!aaa) {
404                         free(*rret);
405                         return -1;
406                 }
407                 sprintf(aaa, "GOTO %s|%s", room, passwd);
408         } else {
409                 aaa = (char *)malloc(strlen(room) + 6);
410                 if (!aaa) {
411                         free(*rret);
412                         return -1;
413                 }
414                 sprintf(aaa, "GOTO %s", room);
415         }
416         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
417         if (ret / 100 == 2) {
418                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
419                 rret[0]->RRunread = extract_long(cret, 1);
420                 rret[0]->RRtotal = extract_long(cret, 2);
421                 rret[0]->RRinfoupdated = extract_int(cret, 3);
422                 rret[0]->RRflags = extract_int(cret, 4);
423                 rret[0]->RRhighest = extract_long(cret, 5);
424                 rret[0]->RRlastread = extract_long(cret, 6);
425                 rret[0]->RRismailbox = extract_int(cret, 7);
426                 rret[0]->RRaide = extract_int(cret, 8);
427                 rret[0]->RRnewmail = extract_long(cret, 9);
428                 rret[0]->RRfloor = extract_int(cret, 10);
429                 rret[0]->RRflags2 = extract_int(cret, 14);
430         } else {
431                 free(*rret);
432                 *rret = NULL;
433         }
434         free(aaa);
435         return ret;
436 }
437
438
439 /* MSGS */
440 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
441 /* whicharg is number of messages, applies to last, first, gt, lt */
442 int CtdlIPCGetMessages(CtdlIPC *ipc, enum MessageList which, int whicharg,
443                 const char *mtemplate, unsigned long **mret, char *cret)
444 {
445         register int ret;
446         register unsigned long count = 0;
447         static char *proto[] =
448                 { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
449         char aaa[33];
450         char *bbb = NULL;
451         size_t bbb_len;
452
453         if (!cret) return -2;
454         if (!mret) return -2;
455         if (*mret) return -2;
456         if (which < 0 || which > 6) return -2;
457
458         if (which <= 2)
459                 sprintf(aaa, "MSGS %s||%d", proto[which],
460                                 (mtemplate) ? 1 : 0);
461         else
462                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
463                                 (mtemplate) ? 1 : 0);
464         if (mtemplate) count = strlen(mtemplate);
465         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
466         if (ret / 100 != 1)
467                 return ret;
468         count = 0;
469         *mret = (unsigned long *)calloc(1, sizeof(unsigned long));
470         if (!*mret)
471                 return -1;
472         while (bbb && strlen(bbb)) {
473                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
474                 remove_token(bbb, 0, '\n');
475                 *mret = (unsigned long *)realloc(*mret, (size_t)((count + 2) *
476                                         sizeof (unsigned long)));
477                 if (*mret) {
478                         (*mret)[count++] = atol(aaa);
479                         (*mret)[count] = 0L;
480                 } else {
481                         break;
482                 }
483         }
484         if (bbb) free(bbb);
485         return ret;
486 }
487
488
489 /* MSG0, MSG2 */
490 int CtdlIPCGetSingleMessage(CtdlIPC *ipc, long msgnum, int headers, int as_mime,
491                 struct ctdlipcmessage **mret, char *cret)
492 {
493         register int ret;
494         char aaa[SIZ];
495         char *bbb = NULL;
496         size_t bbb_len;
497         int multipart_hunting = 0;
498         char multipart_prefix[128];
499         char encoding[256];
500
501         if (!cret) return -1;
502         if (!mret) return -1;
503         if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
504         if (!*mret) return -1;
505         if (!msgnum) return -1;
506
507         strcpy(encoding, "");
508         strcpy(mret[0]->content_type, "");
509         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
510         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
511         if (ret / 100 == 1) {
512                 if (as_mime != 2) {
513                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
514                         while (strlen(bbb) > 4 && bbb[4] == '=') {
515                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
516                                 remove_token(bbb, 0, '\n');
517
518                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
519                                         mret[0]->nhdr = 1;
520                                 else if (!strncasecmp(aaa, "from=", 5))
521                                         safestrncpy(mret[0]->author, &aaa[5], SIZ);
522                                 else if (!strncasecmp(aaa, "type=", 5))
523                                         mret[0]->type = atoi(&aaa[5]);
524                                 else if (!strncasecmp(aaa, "msgn=", 5))
525                                         safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
526                                 else if (!strncasecmp(aaa, "subj=", 5))
527                                         safestrncpy(mret[0]->subject, &aaa[5], SIZ);
528                                 else if (!strncasecmp(aaa, "rfca=", 5))
529                                         safestrncpy(mret[0]->email, &aaa[5], SIZ);
530                                 else if (!strncasecmp(aaa, "hnod=", 5))
531                                         safestrncpy(mret[0]->hnod, &aaa[5], SIZ);
532                                 else if (!strncasecmp(aaa, "room=", 5))
533                                         safestrncpy(mret[0]->room, &aaa[5], SIZ);
534                                 else if (!strncasecmp(aaa, "node=", 5))
535                                         safestrncpy(mret[0]->node, &aaa[5], SIZ);
536                                 else if (!strncasecmp(aaa, "rcpt=", 5))
537                                         safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
538                                 else if (!strncasecmp(aaa, "wefw=", 5))
539                                         safestrncpy(mret[0]->references, &aaa[5], SIZ);
540                                 else if (!strncasecmp(aaa, "time=", 5))
541                                         mret[0]->time = atol(&aaa[5]);
542
543                                 /* Multipart/alternative prefix & suffix strings help
544                                  * us to determine which part we want to download.
545                                  */
546                                 else if (!strncasecmp(aaa, "pref=", 5)) {
547                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
548                                         if (!strcasecmp(multipart_prefix,
549                                            "multipart/alternative")) {
550                                                 ++multipart_hunting;
551                                         }
552                                 }
553                                 else if (!strncasecmp(aaa, "suff=", 5)) {
554                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
555                                         if (!strcasecmp(multipart_prefix,
556                                            "multipart/alternative")) {
557                                                 ++multipart_hunting;
558                                         }
559                                 }
560
561                                 else if (!strncasecmp(aaa, "part=", 5)) {
562                                         struct parts *ptr, *chain;
563         
564                                         ptr = (struct parts *)calloc(1, sizeof (struct parts));
565                                         if (ptr) {
566
567                                                 /* Fill the buffers for the caller */
568                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
569                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
570                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
571                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
572                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
573                                                 ptr->length = extract_long(&aaa[5], 5);
574                                                 if (!mret[0]->attachments)
575                                                         mret[0]->attachments = ptr;
576                                                 else {
577                                                         chain = mret[0]->attachments;
578                                                         while (chain->next)
579                                                                 chain = chain->next;
580                                                         chain->next = ptr;
581                                                 }
582
583                                                 /* Now handle multipart/alternative */
584                                                 if (multipart_hunting > 0) {
585                                                         if ( (!strcasecmp(ptr->mimetype,
586                                                              "text/plain"))
587                                                            || (!strcasecmp(ptr->mimetype,
588                                                               "text/html")) ) {
589                                                                 strcpy(mret[0]->mime_chosen,
590                                                                         ptr->number);
591                                                         }
592                                                 }
593
594                                         }
595                                 }
596                         }
597                         /* Eliminate "text\n" */
598                         remove_token(bbb, 0, '\n');
599
600                         /* If doing a MIME thing, pull out the extra headers */
601                         if (as_mime == 4) {
602                                 do {
603                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
604                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
605                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
606                                                 striplt(mret[0]->content_type);
607
608                                                 /* strip out ";charset=" portion.  FIXME do something with
609                                                  * the charset (like... convert it) instead of just throwing
610                                                  * it away
611                                                  */
612                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
613                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
614                                                 }
615
616                                         }
617                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
618                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
619                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
620                                                 striplt(mret[0]->mime_chosen);
621                                         }
622                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
623                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
624                                                 strcpy(encoding, &encoding[26]);
625                                                 striplt(encoding);
626                                         }
627                                         remove_token(bbb, 0, '\n');
628                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
629                                 remove_token(bbb, 0, '\n');
630                         }
631
632
633                 }
634                 if (strlen(bbb)) {
635
636                         if ( (!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable")) ) {
637                                 char *ccc = NULL;
638                                 int bytes_decoded = 0;
639                                 ccc = malloc(strlen(bbb) + 32768);
640                                 if (!strcasecmp(encoding, "base64")) {
641                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
642                                 }
643                                 else if (!strcasecmp(encoding, "quoted-printable")) {
644                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
645                                 }
646                                 ccc[bytes_decoded] = 0;
647                                 free(bbb);
648                                 bbb = ccc;
649                         }
650
651                         /* FIXME: Strip trailing whitespace */
652                         bbb = (char *)realloc(bbb, (size_t)(strlen(bbb) + 1));
653
654                 } else {
655                         bbb = (char *)realloc(bbb, 1);
656                         *bbb = '\0';
657                 }
658                 mret[0]->text = bbb;
659         }
660         return ret;
661 }
662
663
664 /* WHOK */
665 int CtdlIPCWhoKnowsRoom(CtdlIPC *ipc, char **listing, char *cret)
666 {
667         register int ret;
668         size_t bytes;
669
670         if (!cret) return -2;
671         if (!listing) return -2;
672         if (*listing) return -2;
673
674         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
675         return ret;
676 }
677
678
679 /* INFO */
680 int CtdlIPCServerInfo(CtdlIPC *ipc, char *cret)
681 {
682         register int ret;
683         size_t bytes;
684         char *listing = NULL;
685         char buf[SIZ];
686
687         if (!cret) return -2;
688
689         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
690         if (ret / 100 == 1) {
691                 int line = 0;
692
693                 while (*listing && strlen(listing)) {
694                         extract_token(buf, listing, 0, '\n', sizeof buf);
695                         remove_token(listing, 0, '\n');
696                         switch (line++) {
697                         case 0:         ipc->ServInfo.pid = atoi(buf);
698                                         break;
699                         case 1:         strcpy(ipc->ServInfo.nodename,buf);
700                                         break;
701                         case 2:         strcpy(ipc->ServInfo.humannode,buf);
702                                         break;
703                         case 3:         strcpy(ipc->ServInfo.fqdn,buf);
704                                         break;
705                         case 4:         strcpy(ipc->ServInfo.software,buf);
706                                         break;
707                         case 5:         ipc->ServInfo.rev_level = atoi(buf);
708                                         break;
709                         case 6:         strcpy(ipc->ServInfo.site_location,buf);
710                                         break;
711                         case 7:         strcpy(ipc->ServInfo.sysadm,buf);
712                                         break;
713                         case 9:         strcpy(ipc->ServInfo.moreprompt,buf);
714                                         break;
715                         case 10:        ipc->ServInfo.ok_floors = atoi(buf);
716                                         break;
717                         case 11:        ipc->ServInfo.paging_level = atoi(buf);
718                                         break;
719                         case 13:        ipc->ServInfo.supports_qnop = atoi(buf);
720                                         break;
721                         case 14:        ipc->ServInfo.supports_ldap = atoi(buf);
722                                         break;
723                         case 15:        ipc->ServInfo.newuser_disabled = atoi(buf);
724                                         break;
725                         case 16:        strcpy(ipc->ServInfo.default_cal_zone, buf);
726                                         break;
727                         case 17:        ipc->ServInfo.load_avg = atof(buf);
728                                         break;
729                         case 18:        ipc->ServInfo.worker_avg = atof(buf);
730                                         break;
731                         case 19:        ipc->ServInfo.thread_count = atoi(buf);
732                                         break;
733                         case 20:        ipc->ServInfo.has_sieve = atoi(buf);
734                                         break;
735                         case 21:        ipc->ServInfo.fulltext_enabled = atoi(buf);
736                                         break;
737                         case 22:        strcpy(ipc->ServInfo.svn_revision, buf);
738                                         break;
739                         }
740                 }
741
742         }
743         if (listing) free(listing);
744         return ret;
745 }
746
747
748 /* RDIR */
749 int CtdlIPCReadDirectory(CtdlIPC *ipc, char **listing, char *cret)
750 {
751         register int ret;
752         size_t bytes;
753
754         if (!cret) return -2;
755         if (!listing) return -2;
756         if (*listing) return -2;
757
758         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
759         return ret;
760 }
761
762
763 /*
764  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
765  */
766 int CtdlIPCSetLastRead(CtdlIPC *ipc, long msgnum, char *cret)
767 {
768         register int ret;
769         char aaa[16];
770
771         if (!cret) return -2;
772
773         if (msgnum) {
774                 sprintf(aaa, "SLRP %ld", msgnum);
775         }
776         else {
777                 sprintf(aaa, "SLRP HIGHEST");
778         }
779         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
780         return ret;
781 }
782
783
784 /* INVT */
785 int CtdlIPCInviteUserToRoom(CtdlIPC *ipc, const char *username, char *cret)
786 {
787         register int ret;
788         char *aaa;
789
790         if (!cret) return -2;
791         if (!username) return -2;
792
793         aaa = (char *)malloc(strlen(username) + 6);
794         if (!aaa) return -1;
795
796         sprintf(aaa, "INVT %s", username);
797         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
798         free(aaa);
799         return ret;
800 }
801
802
803 /* KICK */
804 int CtdlIPCKickoutUserFromRoom(CtdlIPC *ipc, const char *username, char *cret)
805 {
806         register int ret;
807         char *aaa;
808
809         if (!cret) return -1;
810         if (!username) return -1;
811
812         aaa = (char *)malloc(strlen(username) + 6);
813
814         sprintf(aaa, "KICK %s", username);
815         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
816         free(aaa);
817         return ret;
818 }
819
820
821 /* GETR */
822 int CtdlIPCGetRoomAttributes(CtdlIPC *ipc, struct ctdlroom **qret, char *cret)
823 {
824         register int ret;
825
826         if (!cret) return -2;
827         if (!qret) return -2;
828         if (!*qret) *qret = (struct ctdlroom *)calloc(1, sizeof (struct ctdlroom));
829         if (!*qret) return -1;
830
831         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
832         if (ret / 100 == 2) {
833                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
834                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
835                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
836                 qret[0]->QRflags = extract_int(cret, 3);
837                 qret[0]->QRfloor = extract_int(cret, 4);
838                 qret[0]->QRorder = extract_int(cret, 5);
839                 qret[0]->QRdefaultview = extract_int(cret, 6);
840                 qret[0]->QRflags2 = extract_int(cret, 7);
841         }
842         return ret;
843 }
844
845
846 /* SETR */
847 /* set forget to kick all users out of room */
848 int CtdlIPCSetRoomAttributes(CtdlIPC *ipc, int forget, struct ctdlroom *qret, char *cret)
849 {
850         register int ret;
851         char *aaa;
852
853         if (!cret) return -2;
854         if (!qret) return -2;
855
856         aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
857                         strlen(qret->QRdirname) + 64);
858         if (!aaa) return -1;
859
860         sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
861                         qret->QRname, qret->QRpasswd, qret->QRdirname,
862                         qret->QRflags, forget, qret->QRfloor, qret->QRorder,
863                         qret->QRdefaultview, qret->QRflags2);
864         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
865         free(aaa);
866         return ret;
867 }
868
869
870 /* GETA */
871 int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
872 {
873         if (!cret) return -1;
874
875         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
876 }
877
878
879 /* SETA */
880 int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret)
881 {
882         register int ret;
883         char *aaa;
884
885         if (!cret) return -2;
886         if (!username) return -2;
887
888         aaa = (char *)malloc(strlen(username) + 6);
889         if (!aaa) return -1;
890
891         sprintf(aaa, "SETA %s", username);
892         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
893         free(aaa);
894         return ret;
895 }
896
897
898 /* ENT0 */
899 int CtdlIPCPostMessage(CtdlIPC *ipc, int flag, int *subject_required,  struct ctdlipcmessage *mr, char *cret)
900 {
901         register int ret;
902         char cmd[SIZ];
903         char *ptr;
904
905         if (!cret) return -2;
906         if (!mr) return -2;
907
908         if (mr->references) {
909                 for (ptr=mr->references; *ptr != 0; ++ptr) {
910                         if (*ptr == '|') *ptr = '!';
911                 }
912         }
913
914         snprintf(cmd, sizeof cmd,
915                         "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
916                         mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
917         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL,
918                         NULL, cret);
919         if ((flag == 0) && (subject_required != NULL)) {
920                 /* Is the server strongly recommending that the user enter a message subject? */
921                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
922                         *subject_required = extract_int(&cret[4], 1);
923                 }
924
925                 
926         }
927         return ret;
928 }
929
930
931 /* RINF */
932 int CtdlIPCRoomInfo(CtdlIPC *ipc, char **iret, char *cret)
933 {
934         size_t bytes;
935
936         if (!cret) return -2;
937         if (!iret) return -2;
938         if (*iret) return -2;
939
940         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
941 }
942
943
944 /* DELE */
945 int CtdlIPCDeleteMessage(CtdlIPC *ipc, long msgnum, char *cret)
946 {
947         char aaa[16];
948
949         if (!cret) return -2;
950         if (!msgnum) return -2;
951
952         sprintf(aaa, "DELE %ld", msgnum);
953         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
954 }
955
956
957 /* MOVE */
958 int CtdlIPCMoveMessage(CtdlIPC *ipc, int copy, long msgnum, const char *destroom, char *cret)
959 {
960         register int ret;
961         char *aaa;
962
963         if (!cret) return -2;
964         if (!destroom) return -2;
965         if (!msgnum) return -2;
966
967         aaa = (char *)malloc(strlen(destroom) + 28);
968         if (!aaa) return -1;
969
970         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
971         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
972         free(aaa);
973         return ret;
974 }
975
976
977 /* KILL */
978 int CtdlIPCDeleteRoom(CtdlIPC *ipc, int for_real, char *cret)
979 {
980         char aaa[16];
981
982         if (!cret) return -2;
983
984         sprintf(aaa, "KILL %d", for_real);
985         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
986 }
987
988
989 /* CRE8 */
990 int CtdlIPCCreateRoom(CtdlIPC *ipc, int for_real, const char *roomname, int type,
991                 const char *password, int floor, char *cret)
992 {
993         register int ret;
994         char *aaa;
995
996         if (!cret) return -2;
997         if (!roomname) return -2;
998
999         if (password) {
1000                 aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
1001                 if (!aaa) return -1;
1002                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
1003                                 password, floor);
1004         } else {
1005                 aaa = (char *)malloc(strlen(roomname) + 40);
1006                 if (!aaa) return -1;
1007                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
1008                                 floor);
1009         }
1010         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1011         free(aaa);
1012         return ret;
1013 }
1014
1015
1016 /* FORG */
1017 int CtdlIPCForgetRoom(CtdlIPC *ipc, char *cret)
1018 {
1019         if (!cret) return -2;
1020
1021         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1022 }
1023
1024
1025 /* MESG */
1026 int CtdlIPCSystemMessage(CtdlIPC *ipc, const char *message, char **mret, char *cret)
1027 {
1028         register int ret;
1029         char *aaa;
1030         size_t bytes;
1031
1032         if (!cret) return -2;
1033         if (!mret) return -2;
1034         if (*mret) return -2;
1035         if (!message) return -2;
1036
1037         aaa = (char *)malloc(strlen(message) + 6);
1038         if (!aaa) return -1;
1039
1040         sprintf(aaa, "MESG %s", message);
1041         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1042         free(aaa);
1043         return ret;
1044 }
1045
1046
1047 /* GNUR */
1048 int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret)
1049 {
1050         if (!cret) return -2;
1051
1052         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1053 }
1054
1055
1056 /* GREG */
1057 int CtdlIPCGetUserRegistration(CtdlIPC *ipc, const char *username, char **rret, char *cret)
1058 {
1059         register int ret;
1060         char *aaa;
1061         size_t bytes;
1062
1063         if (!cret) return -2;
1064         if (!rret) return -2;
1065         if (*rret) return -2;
1066
1067         if (username)
1068                 aaa = (char *)malloc(strlen(username) + 6);
1069         else
1070                 aaa = (char *)malloc(12);
1071         if (!aaa) return -1;
1072
1073         if (username)
1074                 sprintf(aaa, "GREG %s", username);
1075         else
1076                 sprintf(aaa, "GREG _SELF_");
1077         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1078         free(aaa);
1079         return ret;
1080 }
1081
1082
1083 /* VALI */
1084 int CtdlIPCValidateUser(CtdlIPC *ipc, const char *username, int axlevel, char *cret)
1085 {
1086         register int ret;
1087         char *aaa;
1088
1089         if (!cret) return -2;
1090         if (!username) return -2;
1091         if (axlevel < 0 || axlevel > 7) return -2;
1092
1093         aaa = (char *)malloc(strlen(username) + 17);
1094         if (!aaa) return -1;
1095
1096         sprintf(aaa, "VALI %s|%d", username, axlevel);
1097         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1098         free(aaa);
1099         return ret;
1100 }
1101
1102
1103 /* EINF */
1104 int CtdlIPCSetRoomInfo(CtdlIPC *ipc, int for_real, const char *info, char *cret)
1105 {
1106         char aaa[16];
1107
1108         if (!cret) return -1;
1109         if (!info) return -1;
1110
1111         sprintf(aaa, "EINF %d", for_real);
1112         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1113 }
1114
1115
1116 /* LIST */
1117 int CtdlIPCUserListing(CtdlIPC *ipc, char *searchstring, char **listing, char *cret)
1118 {
1119         size_t bytes;
1120         char *cmd;
1121         int ret;
1122
1123         if (!cret) return -1;
1124         if (!listing) return -1;
1125         if (*listing) return -1;
1126         if (!searchstring) return -1;
1127
1128         cmd = malloc(strlen(searchstring) + 10);
1129         sprintf(cmd, "LIST %s", searchstring);
1130
1131         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1132         free(cmd);
1133         return(ret);
1134 }
1135
1136
1137 /* REGI */
1138 int CtdlIPCSetRegistration(CtdlIPC *ipc, const char *info, char *cret)
1139 {
1140         if (!cret) return -1;
1141         if (!info) return -1;
1142
1143         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info),
1144                         NULL, NULL, cret);
1145 }
1146
1147
1148 /* CHEK */
1149 int CtdlIPCMiscCheck(CtdlIPC *ipc, struct ctdlipcmisc *chek, char *cret)
1150 {
1151         register int ret;
1152
1153         if (!cret) return -1;
1154         if (!chek) return -1;
1155
1156         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1157         if (ret / 100 == 2) {
1158                 chek->newmail = extract_long(cret, 0);
1159                 chek->needregis = extract_int(cret, 1);
1160                 chek->needvalid = extract_int(cret, 2);
1161         }
1162         return ret;
1163 }
1164
1165
1166 /* DELF */
1167 int CtdlIPCDeleteFile(CtdlIPC *ipc, const char *filename, char *cret)
1168 {
1169         register int ret;
1170         char *aaa;
1171
1172         if (!cret) return -2;
1173         if (!filename) return -2;
1174         
1175         aaa = (char *)malloc(strlen(filename) + 6);
1176         if (!aaa) return -1;
1177
1178         sprintf(aaa, "DELF %s", filename);
1179         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1180         free(aaa);
1181         return ret;
1182 }
1183
1184
1185 /* MOVF */
1186 int CtdlIPCMoveFile(CtdlIPC *ipc, const char *filename, const char *destroom, char *cret)
1187 {
1188         register int ret;
1189         char *aaa;
1190
1191         if (!cret) return -2;
1192         if (!filename) return -2;
1193         if (!destroom) return -2;
1194
1195         aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
1196         if (!aaa) return -1;
1197
1198         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1199         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1200         free(aaa);
1201         return ret;
1202 }
1203
1204
1205 /* RWHO */
1206 int CtdlIPCOnlineUsers(CtdlIPC *ipc, char **listing, time_t *stamp, char *cret)
1207 {
1208         register int ret;
1209         size_t bytes;
1210
1211         if (!cret) return -1;
1212         if (!listing) return -1;
1213         if (*listing) return -1;
1214
1215         *stamp = CtdlIPCServerTime(ipc, cret);
1216         if (!*stamp)
1217                 *stamp = time(NULL);
1218         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1219         return ret;
1220 }
1221
1222
1223 /* OPEN */
1224 int CtdlIPCFileDownload(CtdlIPC *ipc, const char *filename, void **buf,
1225                 size_t resume,
1226                 void (*progress_gauge_callback)
1227                         (CtdlIPC*, unsigned long, unsigned long),
1228                 char *cret)
1229 {
1230         register int ret;
1231         size_t bytes;
1232         time_t last_mod;
1233         char mimetype[SIZ];
1234         char *aaa;
1235
1236         if (!cret) return -2;
1237         if (!filename) return -2;
1238         if (!buf) return -2;
1239         if (*buf) return -2;
1240         if (ipc->downloading) return -2;
1241
1242         aaa = (char *)malloc(strlen(filename) + 6);
1243         if (!aaa) return -1;
1244
1245         sprintf(aaa, "OPEN %s", filename);
1246         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1247         free(aaa);
1248         if (ret / 100 == 2) {
1249                 ipc->downloading = 1;
1250                 bytes = extract_long(cret, 0);
1251                 last_mod = extract_int(cret, 1);
1252                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1253
1254                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume,
1255                                         progress_gauge_callback, cret);
1256                 /*
1257                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1258                                         progress_gauge_callback, cret);
1259                 */
1260
1261                 ret = CtdlIPCEndDownload(ipc, cret);
1262                 if (ret / 100 == 2)
1263                         sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1264                                         filename, mimetype);
1265         }
1266         return ret;
1267 }
1268
1269
1270 /* OPNA */
1271 int CtdlIPCAttachmentDownload(CtdlIPC *ipc, long msgnum, const char *part,
1272                 void **buf,
1273                 void (*progress_gauge_callback)
1274                         (CtdlIPC*, unsigned long, unsigned long),
1275                 char *cret)
1276 {
1277         register int ret;
1278         size_t bytes;
1279         time_t last_mod;
1280         char filename[SIZ];
1281         char mimetype[SIZ];
1282         char aaa[SIZ];
1283
1284         if (!cret) return -2;
1285         if (!buf) return -2;
1286         if (*buf) return -2;
1287         if (!part) return -2;
1288         if (!msgnum) return -2;
1289         if (ipc->downloading) return -2;
1290
1291         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1292         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1293         if (ret / 100 == 2) {
1294                 ipc->downloading = 1;
1295                 bytes = extract_long(cret, 0);
1296                 last_mod = extract_int(cret, 1);
1297                 extract_token(filename, cret, 2, '|', sizeof filename);
1298                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1299                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1300                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1301                 ret = CtdlIPCEndDownload(ipc, cret);
1302                 if (ret / 100 == 2)
1303                         sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1304                                         filename, mimetype);
1305         }
1306         return ret;
1307 }
1308
1309
1310 /* OIMG */
1311 int CtdlIPCImageDownload(CtdlIPC *ipc, const char *filename, void **buf,
1312                 void (*progress_gauge_callback)
1313                         (CtdlIPC*, unsigned long, unsigned long),
1314                 char *cret)
1315 {
1316         register int ret;
1317         size_t bytes;
1318         time_t last_mod;
1319         char mimetype[SIZ];
1320         char *aaa;
1321
1322         if (!cret) return -1;
1323         if (!buf) return -1;
1324         if (*buf) return -1;
1325         if (!filename) return -1;
1326         if (ipc->downloading) return -1;
1327
1328         aaa = (char *)malloc(strlen(filename) + 6);
1329         if (!aaa) return -1;
1330
1331         sprintf(aaa, "OIMG %s", filename);
1332         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1333         free(aaa);
1334         if (ret / 100 == 2) {
1335                 ipc->downloading = 1;
1336                 bytes = extract_long(cret, 0);
1337                 last_mod = extract_int(cret, 1);
1338                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1339 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1340                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1341                 ret = CtdlIPCEndDownload(ipc, cret);
1342                 if (ret / 100 == 2)
1343                         sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1344                                         filename, mimetype);
1345         }
1346         return ret;
1347 }
1348
1349
1350 /* UOPN */
1351 int CtdlIPCFileUpload(CtdlIPC *ipc, const char *save_as, const char *comment, 
1352                 const char *path, 
1353                 void (*progress_gauge_callback)
1354                         (CtdlIPC*, unsigned long, unsigned long),
1355                 char *cret)
1356 {
1357         register int ret;
1358         char *aaa;
1359         FILE *uploadFP;
1360         char MimeTestBuf[64];
1361         const char *MimeType;
1362         long len;
1363
1364         if (!cret) return -1;
1365         if (!save_as) return -1;
1366         if (!comment) return -1;
1367         if (!path) return -1;
1368         if (!*path) return -1;
1369         if (ipc->uploading) return -1;
1370
1371         uploadFP = fopen(path, "r");
1372         if (!uploadFP) return -2;
1373
1374         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1375         rewind (uploadFP);
1376         if (len < 0) 
1377                 return -3;
1378
1379         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1380         aaa = (char *)malloc(strlen(save_as) + strlen(comment) + 7);
1381         if (!aaa) return -1;
1382
1383         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType,  comment);
1384         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1385         free(aaa);
1386         if (ret / 100 == 2) {
1387                 ipc->uploading = 1;
1388                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1389                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1390                 ipc->uploading = 0;
1391         }
1392         return ret;
1393 }
1394
1395
1396 /* UIMG */
1397 int CtdlIPCImageUpload(CtdlIPC *ipc, int for_real, const char *path,
1398                 const char *save_as,
1399                 void (*progress_gauge_callback)
1400                         (CtdlIPC*, unsigned long, unsigned long),
1401                 char *cret)
1402 {
1403         register int ret;
1404         FILE *uploadFP;
1405         char *aaa;
1406         char MimeTestBuf[64];
1407         const char *MimeType;
1408         long len;
1409
1410         if (!cret) return -1;
1411         if (!save_as) return -1;
1412         if (!path && for_real) return -1;
1413         if (!*path && for_real) return -1;
1414         if (ipc->uploading) return -1;
1415
1416         aaa = (char *)malloc(strlen(save_as) + 17);
1417         if (!aaa) return -1;
1418
1419         uploadFP = fopen(path, "r");
1420         if (!uploadFP) return -2;
1421
1422         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1423         rewind (uploadFP);
1424         if (len < 0) 
1425                 return -3;
1426         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1427
1428         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1429         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1430         free(aaa);
1431         if (ret / 100 == 2 && for_real) {
1432                 ipc->uploading = 1;
1433                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1434                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1435                 ipc->uploading = 0;
1436         }
1437         return ret;
1438 }
1439
1440
1441 /* QUSR */
1442 int CtdlIPCQueryUsername(CtdlIPC *ipc, const char *username, char *cret)
1443 {
1444         register int ret;
1445         char *aaa;
1446
1447         if (!cret) return -2;
1448         if (!username) return -2;
1449
1450         aaa = (char *)malloc(strlen(username) + 6);
1451         if (!aaa) return -1;
1452
1453         sprintf(aaa, "QUSR %s", username);
1454         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1455         free(aaa);
1456         return ret;
1457 }
1458
1459
1460 /* LFLR */
1461 int CtdlIPCFloorListing(CtdlIPC *ipc, char **listing, char *cret)
1462 {
1463         size_t bytes;
1464
1465         if (!cret) return -2;
1466         if (!listing) return -2;
1467         if (*listing) return -2;
1468
1469         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1470 }
1471
1472
1473 /* CFLR */
1474 int CtdlIPCCreateFloor(CtdlIPC *ipc, int for_real, const char *name, char *cret)
1475 {
1476         register int ret;
1477         char aaa[SIZ];
1478
1479         if (!cret) return -2;
1480         if (!name) return -2;
1481
1482         sprintf(aaa, "CFLR %s|%d", name, for_real);
1483         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1484         return ret;
1485 }
1486
1487
1488 /* KFLR */
1489 int CtdlIPCDeleteFloor(CtdlIPC *ipc, int for_real, int floornum, char *cret)
1490 {
1491         char aaa[SIZ];
1492
1493         if (!cret) return -1;
1494         if (floornum < 0) return -1;
1495
1496         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1497         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1498 }
1499
1500
1501 /* EFLR */
1502 int CtdlIPCEditFloor(CtdlIPC *ipc, int floornum, const char *floorname, char *cret)
1503 {
1504         register int ret;
1505         char aaa[SIZ];
1506
1507         if (!cret) return -2;
1508         if (!floorname) return -2;
1509         if (floornum < 0) return -2;
1510
1511         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1512         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1513         return ret;
1514 }
1515
1516
1517 /*
1518  * IDEN 
1519  *
1520  * You only need to fill out hostname, the defaults will be used if any of the
1521  * other fields are not set properly.
1522  */
1523 int CtdlIPCIdentifySoftware(CtdlIPC *ipc, int developerid, int clientid,
1524                 int revision, const char *software_name, const char *hostname,
1525                 char *cret)
1526 {
1527         register int ret;
1528         char *aaa;
1529
1530         if (developerid < 0 || clientid < 0 || revision < 0 ||
1531             !software_name) {
1532                 developerid = 8;
1533                 clientid = 0;
1534                 revision = REV_LEVEL - 600;
1535                 software_name = "Citadel (libcitadel)";
1536         }
1537         if (!hostname) return -2;
1538
1539         aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
1540         if (!aaa) return -1;
1541
1542         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
1543                         revision, software_name, hostname);
1544         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1545         free(aaa);
1546         return ret;
1547 }
1548
1549
1550 /* SEXP */
1551 int CtdlIPCSendInstantMessage(CtdlIPC *ipc, const char *username, const char *text,
1552                 char *cret)
1553 {
1554         register int ret;
1555         char *aaa;
1556
1557         if (!cret) return -2;
1558         if (!username) return -2;
1559
1560         aaa = (char *)malloc(strlen(username) + 8);
1561         if (!aaa) return -1;
1562
1563         if (text) {
1564                 sprintf(aaa, "SEXP %s|-", username);
1565                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text),
1566                                 NULL, NULL, cret);
1567         } else {
1568                 sprintf(aaa, "SEXP %s||", username);
1569                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1570         }
1571         free(aaa);
1572         return ret;
1573 }
1574
1575
1576 /* GEXP */
1577 int CtdlIPCGetInstantMessage(CtdlIPC *ipc, char **listing, char *cret)
1578 {
1579         size_t bytes;
1580
1581         if (!cret) return -2;
1582         if (!listing) return -2;
1583         if (*listing) return -2;
1584
1585         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1586 }
1587
1588
1589 /* DEXP */
1590 /* mode is 0 = enable, 1 = disable, 2 = status */
1591 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC *ipc, int mode, char *cret)
1592 {
1593         char aaa[16];
1594
1595         if (!cret) return -2;
1596
1597         sprintf(aaa, "DEXP %d", mode);
1598         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1599 }
1600
1601
1602 /* EBIO */
1603 int CtdlIPCSetBio(CtdlIPC *ipc, char *bio, char *cret)
1604 {
1605         if (!cret) return -2;
1606         if (!bio) return -2;
1607
1608         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio),
1609                         NULL, NULL, cret);
1610 }
1611
1612
1613 /* RBIO */
1614 int CtdlIPCGetBio(CtdlIPC *ipc, const char *username, char **listing, char *cret)
1615 {
1616         register int ret;
1617         size_t bytes;
1618         char *aaa;
1619
1620         if (!cret) return -2;
1621         if (!username) return -2;
1622         if (!listing) return -2;
1623         if (*listing) return -2;
1624
1625         aaa = (char *)malloc(strlen(username) + 6);
1626         if (!aaa) return -1;
1627
1628         sprintf(aaa, "RBIO %s", username);
1629         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1630         free(aaa);
1631         return ret;
1632 }
1633
1634
1635 /* LBIO */
1636 int CtdlIPCListUsersWithBios(CtdlIPC *ipc, char **listing, char *cret)
1637 {
1638         size_t bytes;
1639
1640         if (!cret) return -2;
1641         if (!listing) return -2;
1642         if (*listing) return -2;
1643
1644         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1645 }
1646
1647
1648 /* STEL */
1649 int CtdlIPCStealthMode(CtdlIPC *ipc, int mode, char *cret)
1650 {
1651         char aaa[16];
1652
1653         if (!cret) return -1;
1654
1655         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1656         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1657 }
1658
1659
1660 /* TERM */
1661 int CtdlIPCTerminateSession(CtdlIPC *ipc, int sid, char *cret)
1662 {
1663         char aaa[16];
1664
1665         if (!cret) return -1;
1666
1667         sprintf(aaa, "TERM %d", sid);
1668         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1669 }
1670
1671
1672 /* DOWN */
1673 int CtdlIPCTerminateServerNow(CtdlIPC *ipc, char *cret)
1674 {
1675         if (!cret) return -1;
1676
1677         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1678 }
1679
1680
1681 /* SCDN */
1682 int CtdlIPCTerminateServerScheduled(CtdlIPC *ipc, int mode, char *cret)
1683 {
1684         char aaa[16];
1685
1686         if (!cret) return -1;
1687
1688         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1689         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1690 }
1691
1692
1693 /* EMSG */
1694 int CtdlIPCEnterSystemMessage(CtdlIPC *ipc, const char *filename, const char *text,
1695                 char *cret)
1696 {
1697         register int ret;
1698         char *aaa;
1699
1700         if (!cret) return -2;
1701         if (!text) return -2;
1702         if (!filename) return -2;
1703
1704         aaa = (char *)malloc(strlen(filename) + 6);
1705         if (!aaa) return -1;
1706
1707         sprintf(aaa, "EMSG %s", filename);
1708         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1709         free(aaa);
1710         return ret;
1711 }
1712
1713
1714 /* HCHG */
1715 int CtdlIPCChangeHostname(CtdlIPC *ipc, const char *hostname, char *cret)
1716 {
1717         register int ret;
1718         char *aaa;
1719
1720         if (!cret) return -2;
1721         if (!hostname) return -2;
1722
1723         aaa = (char *)malloc(strlen(hostname) + 6);
1724         if (!aaa) return -1;
1725
1726         sprintf(aaa, "HCHG %s", hostname);
1727         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1728         free(aaa);
1729         return ret;
1730 }
1731
1732
1733 /* RCHG */
1734 int CtdlIPCChangeRoomname(CtdlIPC *ipc, const char *roomname, char *cret)
1735 {
1736         register int ret;
1737         char *aaa;
1738
1739         if (!cret) return -2;
1740         if (!roomname) return -2;
1741
1742         aaa = (char *)malloc(strlen(roomname) + 6);
1743         if (!aaa) return -1;
1744
1745         sprintf(aaa, "RCHG %s", roomname);
1746         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1747         free(aaa);
1748         return ret;
1749 }
1750
1751
1752 /* UCHG */
1753 int CtdlIPCChangeUsername(CtdlIPC *ipc, const char *username, char *cret)
1754 {
1755         register int ret;
1756         char *aaa;
1757
1758         if (!cret) return -2;
1759         if (!username) return -2;
1760
1761         aaa = (char *)malloc(strlen(username) + 6);
1762         if (!aaa) return -1;
1763
1764         sprintf(aaa, "UCHG %s", username);
1765         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1766         free(aaa);
1767         return ret;
1768 }
1769
1770
1771 /* TIME */
1772 /* This function returns the actual server time reported, or 0 if error */
1773 time_t CtdlIPCServerTime(CtdlIPC *ipc, char *cret)
1774 {
1775         register time_t tret;
1776         register int ret;
1777
1778         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1779         if (ret / 100 == 2) {
1780                 tret = extract_long(cret, 0);
1781         } else {
1782                 tret = 0L;
1783         }
1784         return tret;
1785 }
1786
1787
1788 /* AGUP */
1789 int CtdlIPCAideGetUserParameters(CtdlIPC *ipc, const char *who,
1790                                  struct ctdluser **uret, char *cret)
1791 {
1792         register int ret;
1793         char aaa[SIZ];
1794
1795         if (!cret) return -2;
1796         if (!uret) return -2;
1797         if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof(struct ctdluser));
1798         if (!*uret) return -1;
1799
1800         sprintf(aaa, "AGUP %s", who);
1801         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1802
1803         if (ret / 100 == 2) {
1804                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1805                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1806                 uret[0]->flags = extract_int(cret, 2);
1807                 uret[0]->timescalled = extract_long(cret, 3);
1808                 uret[0]->posted = extract_long(cret, 4);
1809                 uret[0]->axlevel = extract_int(cret, 5);
1810                 uret[0]->usernum = extract_long(cret, 6);
1811                 uret[0]->lastcall = extract_long(cret, 7);
1812                 uret[0]->USuserpurge = extract_int(cret, 8);
1813         }
1814         return ret;
1815 }
1816
1817
1818 /* ASUP */
1819 int CtdlIPCAideSetUserParameters(CtdlIPC *ipc, const struct ctdluser *uret, char *cret)
1820 {
1821         register int ret;
1822         char *aaa;
1823
1824         if (!cret) return -2;
1825         if (!uret) return -2;
1826
1827         aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1828         if (!aaa) return -1;
1829
1830         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1831                         uret->fullname, uret->password, uret->flags,
1832                         uret->timescalled, uret->posted, uret->axlevel,
1833                         uret->usernum, uret->lastcall, uret->USuserpurge);
1834         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1835         free(aaa);
1836         return ret;
1837 }
1838
1839
1840 /* GPEX */
1841 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1842 /* caller must free the struct ExpirePolicy */
1843 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, int which,
1844                 struct ExpirePolicy **policy, char *cret)
1845 {
1846         static char *proto[] = {"room", "floor", "site", "mailboxes" };
1847         char cmd[256];
1848         register int ret;
1849
1850         if (!cret) return -2;
1851         if (!policy) return -2;
1852         if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
1853         if (!*policy) return -1;
1854         if (which < 0 || which > 3) return -2;
1855         
1856         sprintf(cmd, "GPEX %s", proto[which]);
1857         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
1858         if (ret / 100 == 2) {
1859                 policy[0]->expire_mode = extract_int(cret, 0);
1860                 policy[0]->expire_value = extract_int(cret, 1);
1861         }
1862         return ret;
1863 }
1864
1865
1866 /* SPEX */
1867 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1868 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
1869 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
1870                 struct ExpirePolicy *policy, char *cret)
1871 {
1872         char aaa[38];
1873         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
1874
1875         if (!cret) return -2;
1876         if (which < 0 || which > 3) return -2;
1877         if (!policy) return -2;
1878         if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
1879         if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
1880
1881         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
1882                         policy->expire_mode, policy->expire_value);
1883         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1884 }
1885
1886
1887 /* CONF GET */
1888 int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
1889 {
1890         size_t bytes;
1891
1892         if (!cret) return -2;
1893         if (!listing) return -2;
1894         if (*listing) return -2;
1895
1896         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
1897                         listing, &bytes, cret);
1898 }
1899
1900
1901 /* CONF SET */
1902 int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
1903 {
1904         if (!cret) return -2;
1905         if (!listing) return -2;
1906
1907         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
1908                         NULL, NULL, cret);
1909 }
1910
1911
1912 /* CONF GETSYS */
1913 int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype,
1914                 char **listing, char *cret)
1915 {
1916         register int ret;
1917         char *aaa;
1918         size_t bytes;
1919
1920         if (!cret) return -2;
1921         if (!mimetype) return -2;
1922         if (!listing) return -2;
1923         if (*listing) return -2;
1924
1925         aaa = malloc(strlen(mimetype) + 13);
1926         if (!aaa) return -1;
1927         sprintf(aaa, "CONF GETSYS|%s", mimetype);
1928         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0,
1929                         listing, &bytes, cret);
1930     free(aaa);
1931     return ret;
1932 }
1933
1934
1935 /* CONF PUTSYS */
1936 int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype,
1937                const char *listing, char *cret)
1938 {
1939     register int ret;
1940         char *aaa;
1941
1942         if (!cret) return -2;
1943         if (!mimetype) return -2;
1944         if (!listing) return -2;
1945
1946         aaa = malloc(strlen(mimetype) + 13);
1947         if (!aaa) return -1;
1948         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
1949         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing),
1950                         NULL, NULL, cret);
1951     free(aaa);
1952     return ret;
1953 }
1954
1955
1956 /* GNET */
1957 int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
1958 {
1959         size_t bytes;
1960
1961         if (!cret) return -2;
1962         if (!listing) return -2;
1963         if (*listing) return -2;
1964
1965         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0,
1966                         listing, &bytes, cret);
1967 }
1968
1969
1970 /* SNET */
1971 int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
1972 {
1973         if (!cret) return -2;
1974         if (!listing) return -2;
1975
1976         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing),
1977                         NULL, NULL, cret);
1978 }
1979
1980
1981 /* REQT */
1982 int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
1983 {
1984         char aaa[16];
1985
1986         if (!cret) return -2;
1987         if (session < 0) return -2;
1988
1989         sprintf(aaa, "REQT %d", session);
1990         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1991 }
1992
1993
1994 /* SEEN */
1995 int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
1996 {
1997         char aaa[27];
1998
1999         if (!cret) return -2;
2000         if (msgnum < 0) return -2;
2001
2002         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2003         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2004 }
2005
2006
2007 /* STLS */
2008 int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
2009 {
2010         int a;
2011         int r;
2012         char buf[SIZ];
2013
2014 #ifdef HAVE_OPENSSL
2015         SSL *temp_ssl;
2016
2017         /* New SSL object */
2018         temp_ssl = SSL_new(ssl_ctx);
2019         if (!temp_ssl) {
2020                 error_printf("SSL_new failed: %s\n",
2021                                 ERR_reason_error_string(ERR_get_error()));
2022                 return -2;
2023         }
2024         /* Pointless flag waving */
2025 #if SSLEAY_VERSION_NUMBER >= 0x0922
2026         SSL_set_session_id_context(temp_ssl, "Citadel SID", 14);
2027 #endif
2028
2029         if (!access(EGD_POOL, F_OK))
2030                 RAND_egd(EGD_POOL);
2031
2032         if (!RAND_status()) {
2033                 error_printf("PRNG not properly seeded\n");
2034                 return -2;
2035         }
2036
2037         /* Associate network connection with SSL object */
2038         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2039                 error_printf("SSL_set_fd failed: %s\n",
2040                                 ERR_reason_error_string(ERR_get_error()));
2041                 return -2;
2042         }
2043
2044         if (status_hook != NULL)
2045                 status_hook("Requesting encryption...\r");
2046
2047         /* Ready to start SSL/TLS */
2048         /* Old code
2049         CtdlIPC_putline(ipc, "STLS");
2050         CtdlIPC_getline(ipc, buf);
2051         if (buf[0] != '2') {
2052                 error_printf("Server can't start TLS: %s\n", buf);
2053                 return 0;
2054         }
2055         */
2056         r = CtdlIPCGenericCommand(ipc,
2057                                   "STLS", NULL, 0, NULL, NULL, cret);
2058         if (r / 100 != 2) {
2059                 error_printf("Server can't start TLS: %s\n", buf);
2060                 endtls(temp_ssl);
2061                 return r;
2062         }
2063
2064         /* Do SSL/TLS handshake */
2065         if ((a = SSL_connect(temp_ssl)) < 1) {
2066                 error_printf("SSL_connect failed: %s\n",
2067                                 ERR_reason_error_string(ERR_get_error()));
2068                 endtls(temp_ssl);
2069                 return -2;
2070         }
2071         ipc->ssl = temp_ssl;
2072
2073         BIO_set_close(ipc->ssl->rbio, BIO_NOCLOSE);
2074         {
2075                 int bits, alg_bits;
2076
2077                 bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ipc->ssl), &alg_bits);
2078                 error_printf("Encrypting with %s cipher %s (%d of %d bits)\n",
2079                                 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
2080                                 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl)),
2081                                 bits, alg_bits);
2082         }
2083         return r;
2084 #else
2085         return 0;
2086 #endif /* HAVE_OPENSSL */
2087 }
2088
2089
2090 #ifdef HAVE_OPENSSL
2091 static void endtls(SSL *ssl)
2092 {
2093         if (ssl) {
2094                 SSL_shutdown(ssl);
2095                 SSL_free(ssl);
2096         }
2097 }
2098 #endif
2099
2100
2101 /* QDIR */
2102 int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
2103 {
2104     register int ret;
2105         char *aaa;
2106
2107         if (!address) return -2;
2108         if (!cret) return -2;
2109
2110         aaa = (char *)malloc(strlen(address) + 6);
2111         if (!aaa) return -1;
2112
2113         sprintf(aaa, "QDIR %s", address);
2114         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2115     free(aaa);
2116     return ret;
2117 }
2118
2119
2120 /* IPGM */
2121 int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
2122 {
2123         char aaa[30];
2124
2125         if (!cret) return -2;
2126         sprintf(aaa, "IPGM %d", secret);
2127         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2128 }
2129
2130
2131 /* FSCK */
2132 int CtdlIPCMessageBaseCheck(CtdlIPC *ipc, char **mret, char *cret)
2133 {
2134         size_t size = 0;
2135
2136         if (!cret) return -2;
2137         if (!mret) return -2;
2138         if (*mret) return -2;
2139
2140         return CtdlIPCGenericCommand(ipc, "FSCK", NULL, 0, mret, &size, cret);
2141 }
2142
2143
2144 /*
2145  * Not implemented:
2146  * 
2147  * CHAT
2148  * ETLS
2149  * EXPI
2150  * GTLS
2151  * IGAB
2152  * MSG3
2153  * MSG4
2154  * NDOP
2155  * NETP
2156  * NUOP
2157  * SMTP
2158  */
2159
2160
2161 /* ************************************************************************** */
2162 /*             Stuff below this line is not for public consumption            */
2163 /* ************************************************************************** */
2164
2165
2166 INLINE void CtdlIPC_lock(CtdlIPC *ipc)
2167 {
2168         if (ipc->network_status_cb) ipc->network_status_cb(1);
2169 #ifdef THREADED_CLIENT
2170         pthread_mutex_lock(&(ipc->mutex));
2171 #endif
2172 }
2173
2174
2175 INLINE void CtdlIPC_unlock(CtdlIPC *ipc)
2176 {
2177 #ifdef THREADED_CLIENT
2178         pthread_mutex_unlock(&(ipc->mutex));
2179 #endif
2180         if (ipc->network_status_cb) ipc->network_status_cb(0);
2181 }
2182
2183
2184 /* Read a listing from the server up to 000.  Append to dest if it exists */
2185 char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
2186 {
2187         size_t length = 0;
2188         size_t linelength;
2189         char *ret = NULL;
2190         char aaa[SIZ];
2191
2192         ret = dest;
2193         if (ret != NULL) {
2194                 length = strlen(ret);
2195         } else {
2196                 length = 0;
2197         }
2198
2199         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2200                 linelength = strlen(aaa);
2201                 ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
2202                 if (ret) {
2203                         strcpy(&ret[length], aaa);
2204                         length += linelength;
2205                         strcpy(&ret[length++], "\n");
2206                 }
2207         }
2208
2209         return(ret);
2210 }
2211
2212
2213 /* Send a listing to the server; generate the ending 000. */
2214 int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
2215 {
2216         char *text;
2217
2218         text = (char *)malloc(strlen(listing) + 6);
2219         if (text) {
2220                 strcpy(text, listing);
2221                 while (text[strlen(text) - 1] == '\n')
2222                         text[strlen(text) - 1] = '\0';
2223                 strcat(text, "\n000");
2224                 CtdlIPC_putline(ipc, text);
2225                 free(text);
2226                 text = NULL;
2227         } else {
2228                 /* Malloc failed but we are committed to send */
2229                 /* This may result in extra blanks at the bottom */
2230                 CtdlIPC_putline(ipc, text);
2231                 CtdlIPC_putline(ipc, "000");
2232         }
2233         return 0;
2234 }
2235
2236
2237 /* Partial read of file from server */
2238 size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
2239 {
2240         register size_t len = 0;
2241         char aaa[SIZ];
2242
2243         if (!buf) return 0;
2244         if (!cret) return 0;
2245         if (bytes < 1) return 0;
2246
2247         CtdlIPC_lock(ipc);
2248         sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
2249         CtdlIPC_putline(ipc, aaa);
2250         CtdlIPC_getline(ipc, aaa);
2251         if (aaa[0] != '6')
2252                 strcpy(cret, &aaa[4]);
2253         else {
2254                 len = extract_long(&aaa[4], 0);
2255                 *buf = (void *)realloc(*buf, (size_t)(offset + len));
2256                 if (*buf) {
2257                         /* I know what I'm doing */
2258                         serv_read(ipc, ((char *)(*buf) + offset), len);
2259                 } else {
2260                         /* We have to read regardless */
2261                         serv_read(ipc, aaa, len);
2262                         len = 0;
2263                 }
2264         }
2265         CtdlIPC_unlock(ipc);
2266         return len;
2267 }
2268
2269
2270 /* CLOS */
2271 int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
2272 {
2273         register int ret;
2274
2275         if (!cret) return -2;
2276         if (!ipc->downloading) return -2;
2277
2278         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2279         if (ret / 100 == 2)
2280                 ipc->downloading = 0;
2281         return ret;
2282 }
2283
2284
2285 /* MSGP */
2286 int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
2287         register int ret;
2288         char cmd[SIZ];
2289         
2290         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2291         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2292         return ret;
2293 }
2294
2295
2296
2297 /* READ */
2298 int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
2299                 void (*progress_gauge_callback)
2300                         (CtdlIPC*, unsigned long, unsigned long),
2301                char *cret)
2302 {
2303         register size_t len;
2304
2305         if (!cret) return -1;
2306         if (!buf) return -1;
2307         if (*buf) return -1;
2308         if (!ipc->downloading) return -1;
2309
2310         len = resume;
2311         if (progress_gauge_callback)
2312                 progress_gauge_callback(ipc, len, bytes);
2313         while (len < bytes) {
2314                 register size_t block;
2315
2316                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2317                 if (block == 0) {
2318                         free(*buf);
2319                         return 0;
2320                 }
2321                 len += block;
2322                 if (progress_gauge_callback)
2323                         progress_gauge_callback(ipc, len, bytes);
2324         }
2325         return len;
2326 }
2327
2328 /* READ - pipelined */
2329 int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
2330                size_t resume,
2331                 void (*progress_gauge_callback)
2332                         (CtdlIPC*, unsigned long, unsigned long),
2333                char *cret)
2334 {
2335         register size_t len;
2336         register int calls;     /* How many calls in the pipeline */
2337         register int i;         /* iterator */
2338         char aaa[4096];
2339
2340         if (!cret) return -1;
2341         if (!buf) return -1;
2342         if (*buf) return -1;
2343         if (!ipc->downloading) return -1;
2344
2345         *buf = (void *)realloc(*buf, bytes - resume);
2346         if (!*buf) return -1;
2347
2348         len = 0;
2349         CtdlIPC_lock(ipc);
2350         if (progress_gauge_callback)
2351                 progress_gauge_callback(ipc, len, bytes);
2352
2353         /* How many calls will be in the pipeline? */
2354         calls = (bytes - resume) / 4096;
2355         if ((bytes - resume) % 4096) calls++;
2356
2357         /* Send all requests at once */
2358         for (i = 0; i < calls; i++) {
2359                 sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
2360                 CtdlIPC_putline(ipc, aaa);
2361         }
2362
2363         /* Receive all responses at once */
2364         for (i = 0; i < calls; i++) {
2365                 CtdlIPC_getline(ipc, aaa);
2366                 if (aaa[0] != '6')
2367                         strcpy(cret, &aaa[4]);
2368                 else {
2369                         len = extract_long(&aaa[4], 0);
2370                         /* I know what I'm doing */
2371                         serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
2372                 }
2373                 if (progress_gauge_callback)
2374                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2375         }
2376         CtdlIPC_unlock(ipc);
2377         return len;
2378 }
2379
2380
2381 /* UCLS */
2382 int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
2383 {
2384         register int ret;
2385         char cmd[8];
2386
2387         if (!cret) return -1;
2388         if (!ipc->uploading) return -1;
2389
2390         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2391         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2392         ipc->uploading = 0;
2393         return ret;
2394 }
2395
2396
2397 /* WRIT */
2398 int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
2399                 void (*progress_gauge_callback)
2400                         (CtdlIPC*, unsigned long, unsigned long),
2401                 char *cret)
2402 {
2403         register int ret = -1;
2404         register size_t offset = 0;
2405         size_t bytes;
2406         char aaa[SIZ];
2407         char buf[4096];
2408         FILE *fd = uploadFP;
2409         int ferr;
2410
2411         if (!cret) return -1;
2412
2413         fseek(fd, 0L, SEEK_END);
2414         bytes = ftell(fd);
2415         rewind(fd);
2416
2417         if (progress_gauge_callback)
2418                 progress_gauge_callback(ipc, 0, bytes);
2419
2420         while (offset < bytes) {
2421                 register size_t to_write;
2422
2423                 /* Read some data in */
2424                 to_write = fread(buf, 1, 4096, fd);
2425                 if (!to_write) {
2426                         if (feof(fd) || ferror(fd)) break;
2427                 }
2428                 sprintf(aaa, "WRIT %d", (int)to_write);
2429                 CtdlIPC_putline(ipc, aaa);
2430                 CtdlIPC_getline(ipc, aaa);
2431                 strcpy(cret, &aaa[4]);
2432                 ret = atoi(aaa);
2433                 if (aaa[0] == '7') {
2434                         to_write = extract_long(&aaa[4], 0);
2435                         
2436                         serv_write(ipc, buf, to_write);
2437                         offset += to_write;
2438                         if (progress_gauge_callback)
2439                                 progress_gauge_callback(ipc, offset, bytes);
2440                         /* Detect short reads and back up if needed */
2441                         /* offset will never be negative anyway */
2442                         fseek(fd, (signed)offset, SEEK_SET);
2443                 } else {
2444                         break;
2445                 }
2446         }
2447         if (progress_gauge_callback)
2448                 progress_gauge_callback(ipc, 1, 1);
2449         ferr = ferror(fd);
2450         fclose(fd);
2451         return (!ferr ? ret : -2);
2452 }
2453
2454
2455 /*
2456  * Generic command method.  This method should handle any server command
2457  * except for CHAT.  It takes the following arguments:
2458  *
2459  * ipc                  The server to speak with
2460  * command              Preformatted command to send to server
2461  * to_send              A text or binary file to send to server
2462  *                      (only sent if server requests it)
2463  * bytes_to_send        The number of bytes in to_send (required if
2464  *                      sending binary, optional if sending listing)
2465  * to_receive           Pointer to a NULL pointer, if the server
2466  *                      sends text or binary we will allocate memory
2467  *                      for the file and stuff it here
2468  * bytes_to_receive     If a file is received, we will store its
2469  *                      byte count here
2470  * proto_response       The protocol response.  Caller must provide
2471  *                      this buffer and ensure that it is at least
2472  *                      128 bytes in length.
2473  *
2474  * This function returns a number equal to the protocol response number,
2475  * -1 if an internal error occurred, -2 if caller provided bad values,
2476  * or 0 - the protocol response number if bad values were found during
2477  * the protocol exchange.
2478  * It stores the protocol response string (minus the number) in 
2479  * protocol_response as described above.  Some commands send additional
2480  * data in this string.
2481  */
2482 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2483                 const char *command, const char *to_send,
2484                 size_t bytes_to_send, char **to_receive, 
2485                 size_t *bytes_to_receive, char *proto_response)
2486 {
2487         char buf[SIZ];
2488         register int ret;
2489         int watch_ssl = 0;
2490
2491         if (!command) return -2;
2492         if (!proto_response) return -2;
2493
2494 #ifdef HAVE_OPENSSL
2495         if (ipc->ssl) watch_ssl = 1;
2496 #endif
2497
2498         CtdlIPC_lock(ipc);
2499         CtdlIPC_putline(ipc, command);
2500         while (1) {
2501                 CtdlIPC_getline(ipc, proto_response);
2502                 if (proto_response[3] == '*')
2503                         instant_msgs = 1;
2504                 ret = atoi(proto_response);
2505                 strcpy(proto_response, &proto_response[4]);
2506                 switch (ret / 100) {
2507                 default:                        /* Unknown, punt */
2508                 case 2:                         /* OK */
2509                 case 3:                         /* MORE_DATA */
2510                 case 5:                         /* ERROR */
2511                         /* Don't need to do anything */
2512                         break;
2513                 case 1:                         /* LISTING_FOLLOWS */
2514                         if (to_receive && !*to_receive && bytes_to_receive) {
2515                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2516                         } else { /* Drain */
2517                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
2518                                 ret = -ret;
2519                         }
2520                         break;
2521                 case 4:                         /* SEND_LISTING */
2522                         if (to_send) {
2523                                 CtdlIPCSendListing(ipc, to_send);
2524                         } else {
2525                                 /* No listing given, fake it */
2526                                 CtdlIPC_putline(ipc, "000");
2527                                 ret = -ret;
2528                         }
2529                         break;
2530                 case 6:                         /* BINARY_FOLLOWS */
2531                         if (to_receive && !*to_receive && bytes_to_receive) {
2532                                 *bytes_to_receive =
2533                                         extract_long(proto_response, 0);
2534                                 *to_receive = (char *)
2535                                         malloc((size_t)*bytes_to_receive);
2536                                 if (!*to_receive) {
2537                                         ret = -1;
2538                                 } else {
2539                                         serv_read(ipc, *to_receive,
2540                                                         *bytes_to_receive);
2541                                 }
2542                         } else {
2543                                 /* Drain */
2544                                 size_t drain;
2545
2546                                 drain = extract_long(proto_response, 0);
2547                                 while (drain > SIZ) {
2548                                         serv_read(ipc, buf, SIZ);
2549                                         drain -= SIZ;
2550                                 }
2551                                 serv_read(ipc, buf, drain);
2552                                 ret = -ret;
2553                         }
2554                         break;
2555                 case 7:                         /* SEND_BINARY */
2556                         if (to_send && bytes_to_send) {
2557                                 serv_write(ipc, to_send, bytes_to_send);
2558                         } else if (bytes_to_send) {
2559                                 /* Fake it, send nulls */
2560                                 size_t fake;
2561
2562                                 fake = bytes_to_send;
2563                                 memset(buf, '\0', SIZ);
2564                                 while (fake > SIZ) {
2565                                         serv_write(ipc, buf, SIZ);
2566                                         fake -= SIZ;
2567                                 }
2568                                 serv_write(ipc, buf, fake);
2569                                 ret = -ret;
2570                         } /* else who knows?  DANGER WILL ROBINSON */
2571                         break;
2572                 case 8:                         /* START_CHAT_MODE */
2573                         if (!strncasecmp(command, "CHAT", 4)) {
2574                                 /* Don't call chatmode with generic! */
2575                                 CtdlIPC_putline(ipc, "/quit");
2576                                 ret = -ret;
2577                         } else {
2578                                 /* In this mode we send then receive listing */
2579                                 if (to_send) {
2580                                         CtdlIPCSendListing(ipc, to_send);
2581                                 } else {
2582                                         /* No listing given, fake it */
2583                                         CtdlIPC_putline(ipc, "000");
2584                                         ret = -ret;
2585                                 }
2586                                 if (to_receive && !*to_receive
2587                                                 && bytes_to_receive) {
2588                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2589                                 } else { /* Drain */
2590                                         while (CtdlIPC_getline(ipc, buf),
2591                                                         strcmp(buf, "000")) ;
2592                                         ret = -ret;
2593                                 }
2594                         }
2595                         break;
2596                 case 9:                         /* ASYNC_MSG */
2597                         /* CtdlIPCDoAsync(ret, proto_response); */
2598                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2599                         break;
2600                 }
2601                 if (ret / 100 != 9)
2602                         break;
2603         }
2604         CtdlIPC_unlock(ipc);
2605         return ret;
2606 }
2607
2608
2609 static int connectsock(char *host, char *service, char *protocol, int defaultPort)
2610 {
2611         struct hostent *phe;
2612         struct servent *pse;
2613         struct protoent *ppe;
2614         struct sockaddr_in sin;
2615         int s, type;
2616
2617         memset(&sin, 0, sizeof(sin));
2618         sin.sin_family = AF_INET;
2619
2620         pse = getservbyname(service, protocol);
2621         if (pse != NULL) {
2622                 sin.sin_port = pse->s_port;
2623         }
2624         else if (atoi(service) > 0) {
2625                 sin.sin_port = htons(atoi(service));
2626         }
2627         else {
2628                 sin.sin_port = htons(defaultPort);
2629         }
2630         phe = gethostbyname(host);
2631         if (phe) {
2632                 memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
2633         } else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
2634                 return -1;
2635         }
2636         if ((ppe = getprotobyname(protocol)) == 0) {
2637                 return -1;
2638         }
2639         if (!strcmp(protocol, "udp")) {
2640                 type = SOCK_DGRAM;
2641         } else {
2642                 type = SOCK_STREAM;
2643         }
2644
2645         s = socket(PF_INET, type, ppe->p_proto);
2646         if (s < 0) {
2647                 return -1;
2648         }
2649
2650         if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
2651                 close(s);
2652                 return -1;
2653         }
2654
2655         return (s);
2656 }
2657
2658 static int uds_connectsock(int *isLocal, char *sockpath)
2659 {
2660         struct sockaddr_un addr;
2661         int s;
2662
2663         memset(&addr, 0, sizeof(addr));
2664         addr.sun_family = AF_UNIX;
2665         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2666
2667         s = socket(AF_UNIX, SOCK_STREAM, 0);
2668         if (s < 0) {
2669                 return -1;
2670         }
2671
2672         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2673                 close(s);
2674                 return -1;
2675         }
2676
2677         *isLocal = 1;
2678         return s;
2679 }
2680
2681
2682 /*
2683  * input binary data from socket
2684  */
2685 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
2686 {
2687         unsigned int len, rlen;
2688
2689 #if defined(HAVE_OPENSSL)
2690         if (ipc->ssl) {
2691                 serv_read_ssl(ipc, buf, bytes);
2692                 return;
2693         }
2694 #endif
2695         len = 0;
2696         while (len < bytes) {
2697                 rlen = read(ipc->sock, &buf[len], bytes - len);
2698                 if (rlen < 1) {
2699                         connection_died(ipc, 0);
2700                         return;
2701                 }
2702                 len += rlen;
2703         }
2704 }
2705
2706
2707 /*
2708  * send binary to server
2709  */
2710 void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2711 {
2712         unsigned int bytes_written = 0;
2713         int retval;
2714
2715 #if defined(HAVE_OPENSSL)
2716         if (ipc->ssl) {
2717                 serv_write_ssl(ipc, buf, nbytes);
2718                 return;
2719         }
2720 #endif
2721         while (bytes_written < nbytes) {
2722                 retval = write(ipc->sock, &buf[bytes_written],
2723                                nbytes - bytes_written);
2724                 if (retval < 1) {
2725                         connection_died(ipc, 0);
2726                         return;
2727                 }
2728                 bytes_written += retval;
2729         }
2730 }
2731
2732
2733 #ifdef HAVE_OPENSSL
2734 /*
2735  * input binary data from encrypted connection
2736  */
2737 static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
2738 {
2739         int len, rlen;
2740         char junk[1];
2741
2742         len = 0;
2743         while (len < bytes) {
2744                 if (SSL_want_read(ipc->ssl)) {
2745                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2746                                 error_printf("SSL_write in serv_read:\n");
2747                                 ERR_print_errors_fp(stderr);
2748                         }
2749                 }
2750                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2751                 if (rlen < 1) {
2752                         long errval;
2753
2754                         errval = SSL_get_error(ipc->ssl, rlen);
2755                         if (errval == SSL_ERROR_WANT_READ ||
2756                                         errval == SSL_ERROR_WANT_WRITE) {
2757                                 sleep(1);
2758                                 continue;
2759                         }
2760 /***
2761  Not sure why we'd want to handle these error codes any differently,
2762  but this definitely isn't the way to handle them.  Someone must have
2763  naively assumed that we could fall back to unencrypted communications,
2764  but all it does is just recursively blow the stack.
2765                         if (errval == SSL_ERROR_ZERO_RETURN ||
2766                                         errval == SSL_ERROR_SSL) {
2767                                 serv_read(ipc, &buf[len], bytes - len);
2768                                 return;
2769                         }
2770  ***/
2771                         error_printf("SSL_read in serv_read: %s\n",
2772                                         ERR_reason_error_string(ERR_peek_error()));
2773                         connection_died(ipc, 1);
2774                         return;
2775                 }
2776                 len += rlen;
2777         }
2778 }
2779
2780
2781 /*
2782  * send binary to server encrypted
2783  */
2784 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2785 {
2786         unsigned int bytes_written = 0;
2787         int retval;
2788         char junk[1];
2789
2790         while (bytes_written < nbytes) {
2791                 if (SSL_want_write(ipc->ssl)) {
2792                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2793                                 error_printf("SSL_read in serv_write:\n");
2794                                 ERR_print_errors_fp(stderr);
2795                         }
2796                 }
2797                 retval = SSL_write(ipc->ssl, &buf[bytes_written],
2798                                 nbytes - bytes_written);
2799                 if (retval < 1) {
2800                         long errval;
2801
2802                         errval = SSL_get_error(ipc->ssl, retval);
2803                         if (errval == SSL_ERROR_WANT_READ ||
2804                                         errval == SSL_ERROR_WANT_WRITE) {
2805                                 sleep(1);
2806                                 continue;
2807                         }
2808                         if (errval == SSL_ERROR_ZERO_RETURN ||
2809                                         errval == SSL_ERROR_SSL) {
2810                                 serv_write(ipc, &buf[bytes_written],
2811                                                 nbytes - bytes_written);
2812                                 return;
2813                         }
2814                         error_printf("SSL_write in serv_write: %s\n",
2815                                         ERR_reason_error_string(ERR_peek_error()));
2816                         connection_died(ipc, 1);
2817                         return;
2818                 }
2819                 bytes_written += retval;
2820         }
2821 }
2822
2823
2824 #ifdef THREADED_CLIENT
2825 static void ssl_lock(int mode, int n, const char *file, int line)
2826 {
2827         if (mode & CRYPTO_LOCK)
2828                 pthread_mutex_lock(Critters[n]);
2829         else
2830                 pthread_mutex_unlock(Critters[n]);
2831 }
2832 #endif /* THREADED_CLIENT */
2833
2834
2835 static void CtdlIPC_init_OpenSSL(void)
2836 {
2837         int a;
2838         SSL_METHOD *ssl_method;
2839         DH *dh;
2840         
2841         /* already done init */
2842         if (ssl_ctx) {
2843                 return;
2844         }
2845
2846         /* Get started */
2847         a = 0;
2848         ssl_ctx = NULL;
2849         dh = NULL;
2850         SSL_load_error_strings();
2851         SSLeay_add_ssl_algorithms();
2852
2853         /* Set up the SSL context in which we will oeprate */
2854         ssl_method = SSLv23_client_method();
2855         ssl_ctx = SSL_CTX_new(ssl_method);
2856         if (!ssl_ctx) {
2857                 error_printf("SSL_CTX_new failed: %s\n",
2858                                 ERR_reason_error_string(ERR_get_error()));
2859                 return;
2860         }
2861         /* Any reasonable cipher we can get */
2862         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2863                 error_printf("No ciphers available for encryption\n");
2864                 return;
2865         }
2866         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2867         
2868         /* Load DH parameters into the context */
2869         dh = DH_new();
2870         if (!dh) {
2871                 error_printf("Can't allocate a DH object: %s\n",
2872                                 ERR_reason_error_string(ERR_get_error()));
2873                 return;
2874         }
2875         if (!(BN_hex2bn(&(dh->p), DH_P))) {
2876                 error_printf("Can't assign DH_P: %s\n",
2877                                 ERR_reason_error_string(ERR_get_error()));
2878                 DH_free(dh);
2879                 return;
2880         }
2881         if (!(BN_hex2bn(&(dh->g), DH_G))) {
2882                 error_printf("Can't assign DH_G: %s\n",
2883                                 ERR_reason_error_string(ERR_get_error()));
2884                 DH_free(dh);
2885                 return;
2886         }
2887         dh->length = DH_L;
2888         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
2889         DH_free(dh);
2890
2891 #ifdef THREADED_CLIENT
2892         /* OpenSSL requires callbacks for threaded clients */
2893         CRYPTO_set_locking_callback(ssl_lock);
2894         CRYPTO_set_id_callback(id_callback);
2895
2896         /* OpenSSL requires us to do semaphores for threaded clients */
2897         Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
2898         if (!Critters) {
2899                 perror("malloc failed");
2900                 exit(1);
2901         } else {
2902                 for (a = 0; a < CRYPTO_num_locks(); a++) {
2903                         Critters[a] = malloc(sizeof (pthread_mutex_t));
2904                         if (!Critters[a]) {
2905                                 perror("malloc failed");
2906                                 exit(1);
2907                         }
2908                         pthread_mutex_init(Critters[a], NULL);
2909                 }
2910         }
2911 #endif /* THREADED_CLIENT */       
2912 }
2913
2914
2915
2916 #ifdef THREADED_CLIENT
2917 static unsigned long id_callback(void) {
2918         return (unsigned long)pthread_self();
2919 }
2920 #endif /* THREADED_CLIENT */
2921 #endif /* HAVE_OPENSSL */
2922
2923
2924 int
2925 ReadNetworkChunk(CtdlIPC* ipc)
2926 {
2927         fd_set read_fd;
2928         int tries;
2929         int ret = 0;
2930         int err = 0;
2931         struct timeval tv;
2932         size_t n;
2933
2934         tv.tv_sec = 1;
2935         tv.tv_usec = 1000;
2936         tries = 0;
2937         n = 0;
2938         while (1)
2939         {
2940                 errno=0;
2941                 FD_ZERO(&read_fd);
2942                 FD_SET(ipc->sock, &read_fd);
2943                 ret = select(ipc->sock+1, &read_fd, NULL, NULL,  &tv);
2944                 
2945 //              fprintf(stderr, "\nselect failed: %d %d %s\n", ret,  err, strerror(err));
2946                 
2947                 if (ret > 0) {
2948                         
2949                         *(ipc->BufPtr) = '\0';
2950 //                      n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
2951                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1, 0);
2952                         if (n > 0) {
2953                                 ipc->BufPtr[n]='\0';
2954                                 ipc->BufUsed += n;
2955                                 return n;
2956                         }
2957                         else 
2958                                 return n;
2959                 }
2960                 else if (ret < 0) {
2961                         if (!(errno == EINTR || errno == EAGAIN))
2962                                 error_printf( "\nselect failed: %d %s\n", err, strerror(err));
2963                         return -1;
2964                 }/*
2965                 else {
2966                         tries ++;
2967                         if (tries >= 10)
2968                         n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
2969                         if (n > 0) {
2970                                 ipc->BufPtr[n]='\0';
2971                                 ipc->BufUsed += n;
2972                                 return n;
2973                         }
2974                         else {
2975                                 connection_died(ipc, 0);
2976                                 return -1;
2977                         }
2978                         }*/
2979         }
2980 }
2981
2982 /*
2983  * input string from socket - implemented in terms of serv_read()
2984  */
2985 #ifdef CHUNKED_READ
2986
2987 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
2988 {
2989         int i, ntries;
2990         char *aptr, *bptr, *aeptr, *beptr;
2991
2992 //      error_printf("---\n");
2993
2994         beptr = buf + SIZ;
2995 #if defined(HAVE_OPENSSL)
2996         if (ipc->ssl) {
2997                 
2998                 /* Read one character at a time. */
2999                 for (i = 0;; i++) {
3000                         serv_read(ipc, &buf[i], 1);
3001                         if (buf[i] == '\n' || i == (SIZ-1))
3002                                 break;
3003                 }
3004                 
3005                 /* If we got a long line, discard characters until the newline. */
3006                 if (i == (SIZ-1))
3007                         while (buf[i] != '\n')
3008                                 serv_read(ipc, &buf[i], 1);
3009                 
3010                 /* Strip the trailing newline (and carriage return, if present) */
3011                 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3012                 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3013         }
3014         else
3015 #endif
3016         {
3017                 if (ipc->Buf == NULL)
3018                 {
3019                         ipc->BufSize = SIZ;
3020                         ipc->Buf = (char*) malloc(ipc->BufSize + 10);
3021                         *(ipc->Buf) = '\0';
3022                         ipc->BufPtr = ipc->Buf;
3023                 }
3024
3025                 ntries = 0;
3026 //              while ((ipc->BufUsed == 0)||(ntries++ > 10))
3027                 if (ipc->BufUsed == 0)
3028                         ReadNetworkChunk(ipc);
3029
3030 ////            if (ipc->BufUsed != 0) while (1)
3031                 bptr = buf;
3032
3033                 while (1)
3034                 {
3035                         aptr = ipc->BufPtr;
3036                         aeptr = ipc->Buf + ipc->BufSize;
3037                         while ((aptr < aeptr) && 
3038                                (bptr < beptr) &&
3039                                (*aptr != '\0') && 
3040                                (*aptr != '\n'))
3041                                 *(bptr++) = *(aptr++);
3042                         if ((*aptr == '\n') && (aptr < aeptr))
3043                         {
3044                                 /* Terminate it right, remove the line breaks */
3045                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3046                                         aptr ++;
3047                                 while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
3048                                         aptr ++;
3049                                 *(bptr++) = '\0';
3050 //                              fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3051                                 if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
3052                                         *(--bptr) = '\0';
3053                                 
3054                                 /* is there more in the buffer we need to read later? */
3055                                 if (ipc->Buf + ipc->BufUsed > aptr)
3056                                 {
3057                                         ipc->BufPtr = aptr;
3058                                 }
3059                                 else
3060                                 {
3061                                         ipc->BufUsed = 0;
3062                                         ipc->BufPtr = ipc->Buf;
3063                                 }
3064 //                              error_printf("----bla6\n");
3065                                 return;
3066                                 
3067                         }/* should we move our read stuf to the bufferstart so we have more space at the end? */
3068                         else if ((ipc->BufPtr != ipc->Buf) && 
3069                                  (ipc->BufUsed > (ipc->BufSize  - (ipc->BufSize / 4))))
3070                         {
3071                                 size_t NewBufSize = ipc->BufSize * 2;
3072                                 int delta = (ipc->BufPtr - ipc->Buf);
3073                                 char *NewBuf;
3074
3075                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3076                                 NewBuf = (char *)malloc (NewBufSize + 10);
3077                                 memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3078                                 free(ipc->Buf);
3079                                 ipc->Buf = ipc->BufPtr = NewBuf;
3080                                 ipc->BufUsed -= delta;
3081                                 ipc->BufSize = NewBufSize;
3082                         }
3083                         if (ReadNetworkChunk(ipc) <0)
3084                         {
3085 //                              error_printf("----bla\n");
3086                                 return;
3087                         }
3088                 }
3089 ///             error_printf("----bl45761%s\nipc->BufUsed");
3090         }
3091 //      error_printf("----bla1\n");
3092 }
3093
3094 #else   /* CHUNKED_READ */
3095
3096 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3097 {
3098         int i;
3099
3100         /* Read one character at a time. */
3101         for (i = 0;; i++) {
3102                 serv_read(ipc, &buf[i], 1);
3103                 if (buf[i] == '\n' || i == (SIZ-1))
3104                         break;
3105         }
3106
3107         /* If we got a long line, discard characters until the newline. */
3108         if (i == (SIZ-1))
3109                 while (buf[i] != '\n')
3110                         serv_read(ipc, &buf[i], 1);
3111
3112         /* Strip the trailing newline (and carriage return, if present) */
3113         if (i>=0 && buf[i] == 10) buf[i--] = 0;
3114         if (i>=0 && buf[i] == 13) buf[i--] = 0;
3115 }
3116
3117
3118 #endif  /* CHUNKED_READ */
3119
3120
3121 void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
3122 {
3123         CtdlIPC_getline(ipc, buf);
3124 }
3125
3126 /*
3127  * send line to server - implemented in terms of serv_write()
3128  */
3129 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
3130 {
3131         char *cmd = NULL;
3132         int len;
3133
3134         len = strlen(buf);
3135         cmd = malloc(len + 2);
3136         if (!cmd) {
3137                 /* This requires no extra memory */
3138                 serv_write(ipc, buf, len);
3139                 serv_write(ipc, "\n", 1);
3140         } else {
3141                 /* This is network-optimized */
3142                 strncpy(cmd, buf, len);
3143                 strcpy(cmd + len, "\n");
3144                 serv_write(ipc, cmd, len + 1);
3145                 free(cmd);
3146         }
3147
3148         ipc->last_command_sent = time(NULL);
3149 }
3150
3151 void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
3152 {
3153         CtdlIPC_putline(ipc, buf);
3154 }
3155
3156
3157 /*
3158  * attach to server
3159  */
3160 CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3161 {
3162         int a;
3163         char cithost[SIZ];
3164         char citport[SIZ];
3165         char sockpath[SIZ];
3166         CtdlIPC* ipc;
3167
3168         ipc = ialloc(CtdlIPC);
3169         if (!ipc) {
3170                 return 0;
3171         }
3172 #if defined(HAVE_OPENSSL)
3173         ipc->ssl = NULL;
3174         CtdlIPC_init_OpenSSL();
3175 #endif
3176 #if defined(HAVE_PTHREAD_H)
3177         pthread_mutex_init(&(ipc->mutex), NULL); /* Default fast mutex */
3178 #endif
3179         ipc->sock = -1;                 /* Not connected */
3180         ipc->isLocal = 0;               /* Not local, of course! */
3181         ipc->downloading = 0;
3182         ipc->uploading = 0;
3183         ipc->last_command_sent = 0L;
3184         ipc->network_status_cb = NULL;
3185         ipc->Buf = NULL;
3186         ipc->BufUsed = 0;
3187         ipc->BufPtr = NULL;
3188
3189         strcpy(cithost, DEFAULT_HOST);  /* default host */
3190         strcpy(citport, DEFAULT_PORT);  /* default port */
3191
3192         /* Allow caller to supply our values (Windows) */
3193         if (hostbuf && strlen(hostbuf) > 0)
3194                 strcpy(cithost, hostbuf);
3195         if (portbuf && strlen(portbuf) > 0)
3196                 strcpy(citport, portbuf);
3197
3198         /* Read host/port from command line if present */
3199         for (a = 0; a < argc; ++a) {
3200                 if (a == 0) {
3201                         /* do nothing */
3202                 } else if (a == 1) {
3203                         strcpy(cithost, argv[a]);
3204                 } else if (a == 2) {
3205                         strcpy(citport, argv[a]);
3206                 } else {
3207                         error_printf("%s: usage: ",argv[0]);
3208                         error_printf("%s [host] [port] ",argv[0]);
3209                         ifree(ipc);
3210                         errno = EINVAL;
3211                         return 0;
3212                 }
3213         }
3214
3215         if ((!strcmp(cithost, "localhost"))
3216            || (!strcmp(cithost, "127.0.0.1"))) {
3217                 ipc->isLocal = 1;
3218         }
3219
3220         /* If we're using a unix domain socket we can do a bunch of stuff */
3221         if (!strcmp(cithost, UDS)) {
3222                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3223                         snprintf(sockpath, sizeof sockpath, file_citadel_socket);
3224                 }
3225                 else {
3226                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3227                 }
3228                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3229                 if (ipc->sock == -1) {
3230                         ifree(ipc);
3231                         return 0;
3232                 }
3233                 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3234                 if (portbuf != NULL) strcpy(portbuf, sockpath);
3235                 return ipc;
3236         }
3237
3238         ipc->sock = connectsock(cithost, citport, "tcp", 504);
3239         if (ipc->sock == -1) {
3240                 ifree(ipc);
3241                 return 0;
3242         }
3243         if (hostbuf != NULL) strcpy(hostbuf, cithost);
3244         if (portbuf != NULL) strcpy(portbuf, citport);
3245         return ipc;
3246 }
3247
3248
3249 /*
3250  * Disconnect and delete the IPC class (destructor)
3251  */
3252 void CtdlIPC_delete(CtdlIPC* ipc)
3253 {
3254 #ifdef HAVE_OPENSSL
3255         if (ipc->ssl) {
3256                 SSL_shutdown(ipc->ssl);
3257                 SSL_free(ipc->ssl);
3258                 ipc->ssl = NULL;
3259         }
3260 #endif
3261         if (ipc->sock > -1) {
3262                 shutdown(ipc->sock, 2); /* Close it up */
3263                 ipc->sock = -1;
3264         }
3265         if (ipc->Buf != NULL)
3266                 free (ipc->Buf);
3267         ipc->Buf = NULL;
3268         ipc->BufPtr = NULL;
3269         ifree(ipc);
3270 }
3271
3272
3273 /*
3274  * Disconnect and delete the IPC class (destructor)
3275  * Also NULLs out the pointer
3276  */
3277 void CtdlIPC_delete_ptr(CtdlIPC** pipc)
3278 {
3279         CtdlIPC_delete(*pipc);
3280         *pipc = NULL;
3281 }
3282
3283
3284 /*
3285  * return the file descriptor of the server socket so we can select() on it.
3286  *
3287  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3288  * rewritten...
3289  */
3290 int CtdlIPC_getsockfd(CtdlIPC* ipc)
3291 {
3292         return ipc->sock;
3293 }
3294
3295
3296 /*
3297  * return one character
3298  *
3299  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3300  * rewritten...
3301  */
3302 char CtdlIPC_get(CtdlIPC* ipc)
3303 {
3304         char buf[2];
3305         char ch;
3306
3307         serv_read(ipc, buf, 1);
3308         ch = (int) buf[0];
3309
3310         return (ch);
3311 }