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