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