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