1 /* GStreamer
2  * Copyright (C) 2011 Axis Communications <dev-gstreamer@axis.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:element-curlsink
22  * @title: curlsink
23  * @short_description: sink that uploads data to a server using libcurl
24  * @see_also:
25  *
26  * This is a network sink that uses libcurl as a client to upload data to
27  * an SMTP server.
28  *
29  * ## Example launch line
30  *
31  * Upload a JPEG file to an SMTP server.
32  *
33  * |[
34  * gst-launch-1.0 filesrc location=image.jpg ! jpegparse ! curlsmtpsink  \
35  *     file-name=image.jpg  \
36  *     location=smtp://smtp.gmail.com:507 \
37  *     user=test passwd=test  \
38  *     subject=my image \
39  *     mail-from="me@gmail.com" \
40  *     mail-rcpt="you@gmail.com,she@gmail.com" \
41  *     use-ssl=TRUE  \
42  *     insecure=TRUE
43  * ]|
44  *
45  */
46 
47 #ifdef HAVE_CONFIG_H
48 #include "config.h"
49 #endif
50 
51 #include <curl/curl.h>
52 #include <string.h>
53 #include <stdio.h>
54 
55 #if HAVE_SYS_SOCKET_H
56 #include <sys/socket.h>
57 #endif
58 #include <sys/types.h>
59 #include <sys/time.h>
60 #if HAVE_PWD_H
61 #include <pwd.h>
62 #endif
63 #if HAVE_NETINET_IN_H
64 #include <netinet/in.h>
65 #endif
66 #include <unistd.h>
67 #if HAVE_NETINET_IP_H
68 #include <netinet/ip.h>
69 #endif
70 #if HAVE_NETINET_TCP_H
71 #include <netinet/tcp.h>
72 #endif
73 #include <sys/stat.h>
74 #include <fcntl.h>
75 
76 #include "gstcurltlssink.h"
77 #include "gstcurlsmtpsink.h"
78 
79 /* Default values */
80 #define GST_CAT_DEFAULT                gst_curl_smtp_sink_debug
81 #define DEFAULT_USE_SSL                FALSE
82 #define DEFAULT_NBR_ATTACHMENTS        1
83 
84 /* MIME definitions */
85 #define MIME_VERSION                   "MIME-version: 1.0"
86 #define BOUNDARY_STRING                "curlsink-boundary"
87 #define BOUNDARY_STRING_END            "--curlsink-boundary--"
88 
89 #define MAIL_RCPT_DELIMITER            ","
90 
91 /* Plugin specific settings */
92 
93 GST_DEBUG_CATEGORY_STATIC (gst_curl_smtp_sink_debug);
94 
95 enum
96 {
97   PROP_0,
98   PROP_MAIL_RCPT,
99   PROP_MAIL_FROM,
100   PROP_SUBJECT,
101   PROP_MESSAGE_BODY,
102   PROP_POP_USER_NAME,
103   PROP_POP_USER_PASSWD,
104   PROP_POP_LOCATION,
105   PROP_NBR_ATTACHMENTS,
106   PROP_CONTENT_TYPE,
107   PROP_USE_SSL
108 };
109 
110 
111 /* Object class function declarations */
112 static void gst_curl_smtp_sink_finalize (GObject * gobject);
113 static void gst_curl_smtp_sink_set_property (GObject * object, guint prop_id,
114     const GValue * value, GParamSpec * pspec);
115 static void gst_curl_smtp_sink_get_property (GObject * object, guint prop_id,
116     GValue * value, GParamSpec * pspec);
117 
118 static gboolean gst_curl_smtp_sink_set_payload_headers_unlocked (GstCurlBaseSink
119     * sink);
120 static gboolean
121 gst_curl_smtp_sink_set_transfer_options_unlocked (GstCurlBaseSink * sink);
122 static void gst_curl_smtp_sink_set_mime_type (GstCurlBaseSink * bcsink,
123     GstCaps * caps);
124 static gboolean gst_curl_smtp_sink_prepare_transfer (GstCurlBaseSink * bcsink);
125 static size_t gst_curl_smtp_sink_transfer_data_buffer (GstCurlBaseSink * sink,
126     void *curl_ptr, size_t block_size, guint * last_chunk);
127 static size_t gst_curl_smtp_sink_flush_data_unlocked (GstCurlBaseSink * bcsink,
128     void *curl_ptr, size_t block_size, gboolean new_file,
129     gboolean close_transfer);
130 
131 /* private functions */
132 
133 static size_t transfer_payload_headers (GstCurlSmtpSink * sink, void *curl_ptr,
134     size_t block_size);
135 
136 #define gst_curl_smtp_sink_parent_class parent_class
137 G_DEFINE_TYPE (GstCurlSmtpSink, gst_curl_smtp_sink, GST_TYPE_CURL_TLS_SINK);
138 
139 static void
gst_curl_smtp_sink_notify_transfer_end_unlocked(GstCurlSmtpSink * sink)140 gst_curl_smtp_sink_notify_transfer_end_unlocked (GstCurlSmtpSink * sink)
141 {
142   GST_LOG ("transfer completed: %d", sink->transfer_end);
143   sink->transfer_end = TRUE;
144   g_cond_signal (&sink->cond_transfer_end);
145 }
146 
147 static void
gst_curl_smtp_sink_wait_for_transfer_end_unlocked(GstCurlSmtpSink * sink)148 gst_curl_smtp_sink_wait_for_transfer_end_unlocked (GstCurlSmtpSink * sink)
149 {
150   GST_LOG ("waiting for final data do be sent: %d", sink->transfer_end);
151 
152   while (!sink->transfer_end) {
153     g_cond_wait (&sink->cond_transfer_end, GST_OBJECT_GET_LOCK (sink));
154   }
155   GST_LOG ("final data sent");
156 }
157 
158 static void
add_final_boundary_unlocked(GstCurlSmtpSink * sink)159 add_final_boundary_unlocked (GstCurlSmtpSink * sink)
160 {
161   GByteArray *array;
162   gchar *boundary_end;
163   gsize len;
164   gint save, state;
165   gchar *data_out;
166 
167   GST_DEBUG ("adding final boundary");
168 
169   array = sink->base64_chunk->chunk_array;
170   g_assert (array);
171 
172   /* it will need up to 5 bytes if line-breaking is enabled
173    * additional byte is needed for <CR> as it is not automatically added by
174    * glib */
175   data_out = g_malloc (6);
176   save = sink->base64_chunk->save;
177   state = sink->base64_chunk->state;
178   len = g_base64_encode_close (TRUE, data_out, &state, &save);
179 
180   /* workaround */
181   data_out[len - 1] = '\r';
182   data_out[len] = '\n';
183 
184   /* +1 for CR */
185   g_byte_array_append (array, (guint8 *) data_out, (guint) (len + 1));
186   g_free (data_out);
187 
188   boundary_end = g_strdup_printf ("\r\n%s\r\n", BOUNDARY_STRING_END);
189   g_byte_array_append (array, (guint8 *) boundary_end, strlen (boundary_end));
190   g_free (boundary_end);
191 
192   sink->final_boundary_added = TRUE;
193 }
194 
195 static gboolean
gst_curl_smtp_sink_event(GstBaseSink * bsink,GstEvent * event)196 gst_curl_smtp_sink_event (GstBaseSink * bsink, GstEvent * event)
197 {
198   GstCurlBaseSink *bcsink = GST_CURL_BASE_SINK (bsink);
199   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bsink);
200 
201   switch (event->type) {
202     case GST_EVENT_EOS:
203       GST_DEBUG_OBJECT (sink, "received EOS");
204       gst_curl_base_sink_set_live (bcsink, FALSE);
205 
206       GST_OBJECT_LOCK (sink);
207       sink->eos = TRUE;
208       if (bcsink->flow_ret == GST_FLOW_OK && sink->base64_chunk != NULL
209           && !sink->final_boundary_added) {
210         add_final_boundary_unlocked (sink);
211         gst_curl_base_sink_transfer_thread_notify_unlocked (bcsink);
212         GST_FIXME_OBJECT (sink, "if gstpoll errors in transfer thread, then "
213             "this wait will never timeout because the transfer thread does "
214             "not signal it upon errors");
215         gst_curl_smtp_sink_wait_for_transfer_end_unlocked (sink);
216       }
217       GST_OBJECT_UNLOCK (sink);
218       break;
219 
220     default:
221       break;
222   }
223 
224   return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
225 }
226 
227 static gboolean
gst_curl_smtp_sink_has_buffered_data_unlocked(GstCurlBaseSink * bcsink)228 gst_curl_smtp_sink_has_buffered_data_unlocked (GstCurlBaseSink * bcsink)
229 {
230   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
231   Base64Chunk *chunk;
232   GByteArray *array = NULL;
233   gboolean ret = FALSE;
234 
235   chunk = sink->base64_chunk;
236 
237   if (chunk) {
238     array = chunk->chunk_array;
239     if (array)
240       ret = (array->len == 0 && sink->final_boundary_added) ? FALSE : TRUE;
241   }
242 
243   return ret;
244 }
245 
246 static void
gst_curl_smtp_sink_class_init(GstCurlSmtpSinkClass * klass)247 gst_curl_smtp_sink_class_init (GstCurlSmtpSinkClass * klass)
248 {
249   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
250   GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
251   GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass;
252   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
253 
254   GST_DEBUG_CATEGORY_INIT (gst_curl_smtp_sink_debug, "curlsmtpsink", 0,
255       "curl smtp sink element");
256   GST_DEBUG_OBJECT (klass, "class_init");
257 
258   gst_element_class_set_static_metadata (element_class,
259       "Curl smtp sink",
260       "Sink/Network",
261       "Upload data over SMTP protocol using libcurl",
262       "Patricia Muscalu <patricia@axis.com>");
263 
264   gstcurlbasesink_class->set_protocol_dynamic_options_unlocked =
265       gst_curl_smtp_sink_set_payload_headers_unlocked;
266   gstcurlbasesink_class->set_options_unlocked =
267       gst_curl_smtp_sink_set_transfer_options_unlocked;
268   gstcurlbasesink_class->set_mime_type = gst_curl_smtp_sink_set_mime_type;
269   gstcurlbasesink_class->prepare_transfer = gst_curl_smtp_sink_prepare_transfer;
270   gstcurlbasesink_class->transfer_data_buffer =
271       gst_curl_smtp_sink_transfer_data_buffer;
272   gstcurlbasesink_class->flush_data_unlocked =
273       gst_curl_smtp_sink_flush_data_unlocked;
274   gstcurlbasesink_class->has_buffered_data_unlocked =
275       gst_curl_smtp_sink_has_buffered_data_unlocked;
276 
277   gstbasesink_class->event = gst_curl_smtp_sink_event;
278   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_smtp_sink_finalize);
279   gobject_class->set_property = gst_curl_smtp_sink_set_property;
280   gobject_class->get_property = gst_curl_smtp_sink_get_property;
281 
282   g_object_class_install_property (gobject_class, PROP_MAIL_RCPT,
283       g_param_spec_string ("mail-rcpt", "Mail recipient",
284           "Single address that the given mail should get sent to", NULL,
285           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
286   g_object_class_install_property (gobject_class, PROP_MAIL_FROM,
287       g_param_spec_string ("mail-from", "Mail sender",
288           "Single address that the given mail should get sent from", NULL,
289           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
290   g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
291       g_param_spec_string ("content-type", "Content type",
292           "The mime type of the body of the request", NULL,
293           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
294   g_object_class_install_property (gobject_class, PROP_SUBJECT,
295       g_param_spec_string ("subject", "UTF-8 encoded mail subject",
296           "Mail subject", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
297   g_object_class_install_property (gobject_class, PROP_MESSAGE_BODY,
298       g_param_spec_string ("message-body", "UTF-8 encoded message body",
299           "Message body", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
300   g_object_class_install_property (gobject_class, PROP_USE_SSL,
301       g_param_spec_boolean ("use-ssl", "Use SSL",
302           "Use SSL/TLS for the connection", DEFAULT_USE_SSL,
303           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
304   g_object_class_install_property (gobject_class, PROP_NBR_ATTACHMENTS,
305       g_param_spec_int ("nbr-attachments", "Number attachments",
306           "Number attachments to send", G_MININT, G_MAXINT,
307           DEFAULT_NBR_ATTACHMENTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
308   g_object_class_install_property (gobject_class, PROP_POP_USER_NAME,
309       g_param_spec_string ("pop-user", "User name",
310           "User name to use for POP server authentication", NULL,
311           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
312   g_object_class_install_property (gobject_class, PROP_POP_USER_PASSWD,
313       g_param_spec_string ("pop-passwd", "User password",
314           "User password to use for POP server authentication", NULL,
315           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
316   g_object_class_install_property (gobject_class, PROP_POP_LOCATION,
317       g_param_spec_string ("pop-location", "POP location",
318           "URL POP used for authentication", NULL,
319           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
320 
321 }
322 
323 static void
gst_curl_smtp_sink_init(GstCurlSmtpSink * sink)324 gst_curl_smtp_sink_init (GstCurlSmtpSink * sink)
325 {
326   sink->curl_recipients = NULL;
327   sink->mail_rcpt = NULL;
328   sink->mail_from = NULL;
329   sink->subject = NULL;
330   sink->message_body = NULL;
331   sink->payload_headers = NULL;
332   sink->base64_chunk = NULL;
333 
334   g_cond_init (&sink->cond_transfer_end);
335   sink->transfer_end = FALSE;
336   sink->eos = FALSE;
337   sink->final_boundary_added = FALSE;
338 
339   sink->reset_transfer_options = FALSE;
340   sink->use_ssl = DEFAULT_USE_SSL;
341 
342   sink->pop_user = NULL;
343   sink->pop_passwd = NULL;
344   sink->pop_location = NULL;
345   sink->pop_curl = NULL;
346 }
347 
348 static void
gst_curl_smtp_sink_finalize(GObject * gobject)349 gst_curl_smtp_sink_finalize (GObject * gobject)
350 {
351   GstCurlSmtpSink *this = GST_CURL_SMTP_SINK (gobject);
352 
353   GST_DEBUG ("finalizing curlsmtpsink");
354 
355   if (this->curl_recipients != NULL) {
356     curl_slist_free_all (this->curl_recipients);
357   }
358   g_free (this->mail_rcpt);
359   g_free (this->mail_from);
360   g_free (this->subject);
361   g_free (this->message_body);
362   g_free (this->content_type);
363 
364   g_cond_clear (&this->cond_transfer_end);
365 
366   if (this->base64_chunk != NULL) {
367     if (this->base64_chunk->chunk_array != NULL) {
368       g_byte_array_free (this->base64_chunk->chunk_array, TRUE);
369     }
370     g_free (this->base64_chunk);
371   }
372 
373   if (this->payload_headers != NULL) {
374     g_byte_array_free (this->payload_headers, TRUE);
375   }
376 
377   g_free (this->pop_user);
378   g_free (this->pop_passwd);
379   if (this->pop_curl != NULL) {
380     curl_easy_cleanup (this->pop_curl);
381     this->pop_curl = NULL;
382   }
383   g_free (this->pop_location);
384 
385   G_OBJECT_CLASS (parent_class)->finalize (gobject);
386 }
387 
388 static void
gst_curl_smtp_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)389 gst_curl_smtp_sink_set_property (GObject * object, guint prop_id,
390     const GValue * value, GParamSpec * pspec)
391 {
392   GstCurlSmtpSink *sink;
393   GstState cur_state;
394 
395   g_return_if_fail (GST_IS_CURL_SMTP_SINK (object));
396   sink = GST_CURL_SMTP_SINK (object);
397 
398   gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0);
399   if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) {
400     GST_OBJECT_LOCK (sink);
401 
402     switch (prop_id) {
403       case PROP_MAIL_RCPT:
404         g_free (sink->mail_rcpt);
405         sink->mail_rcpt = g_value_dup_string (value);
406         GST_DEBUG_OBJECT (sink, "mail-rcpt set to %s", sink->mail_rcpt);
407         break;
408       case PROP_MAIL_FROM:
409         g_free (sink->mail_from);
410         sink->mail_from = g_value_dup_string (value);
411         GST_DEBUG_OBJECT (sink, "mail-from set to %s", sink->mail_from);
412         break;
413       case PROP_SUBJECT:
414         g_free (sink->subject);
415         sink->subject = g_value_dup_string (value);
416         GST_DEBUG_OBJECT (sink, "subject set to %s", sink->subject);
417         break;
418       case PROP_MESSAGE_BODY:
419         g_free (sink->message_body);
420         sink->message_body = g_value_dup_string (value);
421         GST_DEBUG_OBJECT (sink, "message-body set to %s", sink->message_body);
422         break;
423       case PROP_CONTENT_TYPE:
424         g_free (sink->content_type);
425         sink->content_type = g_value_dup_string (value);
426         GST_DEBUG_OBJECT (sink, "content-type set to %s", sink->content_type);
427         break;
428       case PROP_USE_SSL:
429         sink->use_ssl = g_value_get_boolean (value);
430         GST_DEBUG_OBJECT (sink, "use-ssl set to %d", sink->use_ssl);
431         break;
432       case PROP_NBR_ATTACHMENTS:
433         sink->nbr_attachments = g_value_get_int (value);
434         sink->curr_attachment = 1;
435         GST_DEBUG_OBJECT (sink, "nbr-attachments set to %d",
436             sink->nbr_attachments);
437         break;
438       case PROP_POP_USER_NAME:
439         g_free (sink->pop_user);
440         sink->pop_user = g_value_dup_string (value);
441         GST_DEBUG_OBJECT (sink, "pop-user set to %s", sink->pop_user);
442         break;
443       case PROP_POP_USER_PASSWD:
444         g_free (sink->pop_passwd);
445         sink->pop_passwd = g_value_dup_string (value);
446         GST_DEBUG_OBJECT (sink, "pop-passwd set to %s", sink->pop_passwd);
447         break;
448       case PROP_POP_LOCATION:
449         g_free (sink->pop_location);
450         sink->pop_location = g_value_dup_string (value);
451         GST_DEBUG_OBJECT (sink, "pop-location set to %s", sink->pop_location);
452         break;
453 
454       default:
455         GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id);
456         break;
457     }
458 
459     GST_OBJECT_UNLOCK (sink);
460 
461     return;
462   }
463 
464   /* in PLAYING or PAUSED state */
465   GST_OBJECT_LOCK (sink);
466 
467   switch (prop_id) {
468     case PROP_CONTENT_TYPE:
469       g_free (sink->content_type);
470       sink->content_type = g_value_dup_string (value);
471       GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type);
472       break;
473     default:
474       GST_WARNING_OBJECT (sink, "cannot set property when PLAYING");
475       break;
476   }
477 
478   GST_OBJECT_UNLOCK (sink);
479 }
480 
481 static void
gst_curl_smtp_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)482 gst_curl_smtp_sink_get_property (GObject * object, guint prop_id,
483     GValue * value, GParamSpec * pspec)
484 {
485   GstCurlSmtpSink *sink;
486 
487   g_return_if_fail (GST_IS_CURL_SMTP_SINK (object));
488   sink = GST_CURL_SMTP_SINK (object);
489 
490   switch (prop_id) {
491     case PROP_MAIL_RCPT:
492       g_value_set_string (value, sink->mail_rcpt);
493       break;
494     case PROP_MAIL_FROM:
495       g_value_set_string (value, sink->mail_from);
496       break;
497     case PROP_SUBJECT:
498       g_value_set_string (value, sink->subject);
499       break;
500     case PROP_MESSAGE_BODY:
501       g_value_set_string (value, sink->message_body);
502       break;
503     case PROP_CONTENT_TYPE:
504       g_value_set_string (value, sink->content_type);
505       break;
506     case PROP_USE_SSL:
507       g_value_set_boolean (value, sink->use_ssl);
508       break;
509     case PROP_NBR_ATTACHMENTS:
510       g_value_set_int (value, sink->nbr_attachments);
511       break;
512     case PROP_POP_USER_NAME:
513       g_value_set_string (value, sink->pop_user);
514       break;
515     case PROP_POP_USER_PASSWD:
516       g_value_set_string (value, sink->pop_passwd);
517       break;
518     case PROP_POP_LOCATION:
519       g_value_set_string (value, sink->pop_location);
520       break;
521 
522     default:
523       GST_DEBUG_OBJECT (sink, "invalid property id");
524       break;
525   }
526 }
527 
528 static gboolean
gst_curl_smtp_sink_set_payload_headers_unlocked(GstCurlBaseSink * bcsink)529 gst_curl_smtp_sink_set_payload_headers_unlocked (GstCurlBaseSink * bcsink)
530 {
531   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
532   gchar *hdrs;
533   gboolean append_headers = FALSE;
534 
535   if (sink->reset_transfer_options) {
536     g_assert (!bcsink->is_live);
537     sink->reset_transfer_options = FALSE;
538 
539     /* all data has been sent in the previous transfer, setup headers for
540      * a new transfer */
541     gst_curl_smtp_sink_set_transfer_options_unlocked (bcsink);
542     append_headers = TRUE;
543   }
544 
545   if (sink->payload_headers == NULL) {
546     sink->payload_headers = g_byte_array_new ();
547     append_headers = TRUE;
548   }
549 
550   if (sink->base64_chunk == NULL) {
551     g_assert (!bcsink->is_live);
552     /* we are just about to send the very first attachment in this transfer.
553      * This is the only place where base64_chunk and its array are allocated.
554      */
555     sink->base64_chunk = g_malloc (sizeof (Base64Chunk));
556     sink->base64_chunk->chunk_array = g_byte_array_new ();
557     append_headers = TRUE;
558   } else {
559     g_assert (sink->base64_chunk->chunk_array != NULL);
560   }
561 
562   sink->base64_chunk->state = 0;
563   sink->base64_chunk->save = 0;
564 
565   if (G_UNLIKELY (!append_headers)) {
566     g_byte_array_free (sink->base64_chunk->chunk_array, TRUE);
567     sink->base64_chunk->chunk_array = NULL;
568     g_free (sink->base64_chunk);
569     sink->base64_chunk = NULL;
570     return FALSE;
571   }
572 
573   hdrs = g_strdup_printf ("\r\n\r\n--%s\r\n"
574       "Content-Type: application/octet-stream; name=\"%s\"\r\n"
575       /* TODO: support for other encodings */
576       "Content-Transfer-Encoding: BASE64\r\n"
577       "Content-Disposition: attachment; filename=\"%s\"\r\n\r\n"
578       "\r\n", BOUNDARY_STRING, bcsink->file_name, bcsink->file_name);
579   g_byte_array_append (sink->payload_headers, (guint8 *) hdrs, strlen (hdrs));
580   g_free (hdrs);
581 
582   return TRUE;
583 }
584 
585 /* MIME encoded-word syntax (RFC 2047):
586  * =?charset?encoding?encoded text?= */
587 static gchar *
generate_encoded_word(gchar * str)588 generate_encoded_word (gchar * str)
589 {
590   gchar *encoded_word;
591 
592   g_assert (str);
593 
594   if (g_utf8_validate (str, -1, NULL)) {
595     gchar *base64_str;
596 
597     base64_str = g_base64_encode ((const guchar *) str, strlen (str));
598     encoded_word = g_strdup_printf ("=?utf-8?B?%s?=", base64_str);
599     g_free (base64_str);
600   } else {
601     GST_WARNING ("string is not a valid UTF-8 string");
602     encoded_word = g_strdup (str);
603   }
604 
605   /* TODO: 75 character limit */
606   return encoded_word;
607 }
608 
609 /* Setup header fields (From:/To:/Date: etc) and message body for the e-mail.
610  * This data is supposed to be sent to libcurl just before any media data.
611  * This function is called once for each e-mail:
612  * 1. we are about the send the first attachment
613  * 2. we have sent all the attachments and continue sending new ones within
614  *    a new e-mail (transfer options have been reset). */
615 static gboolean
gst_curl_smtp_sink_set_transfer_options_unlocked(GstCurlBaseSink * bcsink)616 gst_curl_smtp_sink_set_transfer_options_unlocked (GstCurlBaseSink * bcsink)
617 {
618   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
619   GstCurlTlsSinkClass *parent_class;
620   gchar *request_headers;
621   GDateTime *date;
622   gchar *date_str;
623   gchar **tmp_list = NULL;
624   gchar *subject_header = NULL;
625   gchar *message_body = NULL;
626   gchar *rcpt_header = NULL;
627   gchar *enc_rcpt;
628   gchar *from_header = NULL;
629   gchar *enc_from;
630   gint i;
631   CURLcode res;
632 
633   g_assert (sink->payload_headers == NULL);
634   g_assert (sink->mail_rcpt != NULL);
635   g_assert (sink->mail_from != NULL);
636 
637   /* time */
638   date = g_date_time_new_now_local ();
639   date_str = g_date_time_format (date, "%a %b %e %H:%M:%S %Y %z");
640   g_date_time_unref (date);
641 
642   /* recipient, sender and subject are all UTF-8 strings, which are additionally
643    * base64-encoded */
644 
645   /* recipient */
646   enc_rcpt = generate_encoded_word (sink->mail_rcpt);
647   rcpt_header = g_strdup_printf ("%s <%s>", enc_rcpt, sink->mail_rcpt);
648   g_free (enc_rcpt);
649 
650   /* sender */
651   enc_from = generate_encoded_word (sink->mail_from);
652   from_header = g_strdup_printf ("%s <%s>", enc_from, sink->mail_from);
653   g_free (enc_from);
654 
655   /* subject */
656   if (sink->subject != NULL) {
657     subject_header = generate_encoded_word (sink->subject);
658   }
659 
660   /* message */
661   if (sink->message_body != NULL) {
662     message_body = g_base64_encode ((const guchar *) sink->message_body,
663         strlen (sink->message_body));
664   }
665 
666   request_headers = g_strdup_printf (
667       /* headers */
668       "To: %s\r\n"
669       "From: %s\r\n"
670       "Subject: %s\r\n"
671       "Date: %s\r\n"
672       MIME_VERSION "\r\n"
673       "Content-Type: multipart/mixed; boundary=%s\r\n" "\r\n"
674       /* body headers */
675       "--" BOUNDARY_STRING "\r\n"
676       "Content-Type: text/plain; charset=utf-8\r\n"
677       "Content-Transfer-Encoding: BASE64\r\n"
678       /* message body */
679       "\r\n%s\r\n",
680       rcpt_header,
681       from_header,
682       subject_header ? subject_header : "",
683       date_str, BOUNDARY_STRING, message_body ? message_body : "");
684 
685   sink->payload_headers = g_byte_array_new ();
686 
687   g_byte_array_append (sink->payload_headers, (guint8 *) request_headers,
688       strlen (request_headers));
689   g_free (date_str);
690   g_free (subject_header);
691   g_free (message_body);
692   g_free (rcpt_header);
693   g_free (from_header);
694   g_free (request_headers);
695 
696   res = curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_FROM, sink->mail_from);
697   if (res != CURLE_OK) {
698     bcsink->error =
699         g_strdup_printf ("failed to set SMTP sender email address: %s",
700         curl_easy_strerror (res));
701     return FALSE;
702   }
703 
704   if (sink->curl_recipients != NULL) {
705     curl_slist_free_all (sink->curl_recipients);
706     sink->curl_recipients = NULL;
707   }
708 
709   tmp_list = g_strsplit_set (sink->mail_rcpt, MAIL_RCPT_DELIMITER, -1);
710   for (i = 0; i < g_strv_length (tmp_list); i++) {
711     sink->curl_recipients = curl_slist_append (sink->curl_recipients,
712         tmp_list[i]);
713   }
714   g_strfreev (tmp_list);
715 
716   /* note that the CURLOPT_MAIL_RCPT takes a list, not a char array */
717   res = curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_RCPT,
718       sink->curl_recipients);
719   if (res != CURLE_OK) {
720     bcsink->error =
721         g_strdup_printf ("failed to set SMTP recipient email address: %s",
722         curl_easy_strerror (res));
723     return FALSE;
724   }
725 
726   res = curl_easy_setopt (bcsink->curl, CURLOPT_UPLOAD, 1L);
727   if (res != CURLE_OK) {
728     bcsink->error = g_strdup_printf ("failed to prepare for upload: %s",
729         curl_easy_strerror (res));
730     return FALSE;
731   }
732 
733   parent_class = GST_CURL_TLS_SINK_GET_CLASS (sink);
734 
735   if (sink->use_ssl) {
736     return parent_class->set_options_unlocked (bcsink);
737   }
738 
739   return TRUE;
740 }
741 
742 /* FIXME: exactly the same function as in http sink */
743 static void
gst_curl_smtp_sink_set_mime_type(GstCurlBaseSink * bcsink,GstCaps * caps)744 gst_curl_smtp_sink_set_mime_type (GstCurlBaseSink * bcsink, GstCaps * caps)
745 {
746   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
747   GstStructure *structure;
748   const gchar *mime_type;
749 
750   if (sink->content_type != NULL) {
751     return;
752   }
753 
754   structure = gst_caps_get_structure (caps, 0);
755   mime_type = gst_structure_get_name (structure);
756   sink->content_type = g_strdup (mime_type);
757 }
758 
759 static size_t
gst_curl_smtp_sink_flush_data_unlocked(GstCurlBaseSink * bcsink,void * curl_ptr,size_t block_size,gboolean new_file,gboolean close_transfer)760 gst_curl_smtp_sink_flush_data_unlocked (GstCurlBaseSink * bcsink,
761     void *curl_ptr, size_t block_size, gboolean new_file,
762     gboolean close_transfer)
763 {
764   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
765   Base64Chunk *chunk = sink->base64_chunk;
766   gint state = chunk->state;
767   gint save = chunk->save;
768   GByteArray *array = chunk->chunk_array;
769   size_t bytes_to_send;
770   gint len;
771   gchar *data_out;
772 
773   GST_DEBUG
774       ("live: %d, num attachments: %d, curr_attachment: %d, "
775       "eos: %d, close_transfer: %d, final boundary: %d, array_len: %d",
776       bcsink->is_live, sink->nbr_attachments, sink->curr_attachment,
777       sink->eos, close_transfer, sink->final_boundary_added, array->len);
778 
779 
780   if ((bcsink->is_live && (sink->curr_attachment == sink->nbr_attachments))
781       || (sink->nbr_attachments == 1) || sink->eos
782       || sink->final_boundary_added) {
783     bcsink->is_live = FALSE;
784     sink->reset_transfer_options = TRUE;
785     sink->final_boundary_added = FALSE;
786     sink->curr_attachment = 1;
787 
788     GST_DEBUG ("returning 0, no more data to send in this transfer");
789 
790     return 0;
791   }
792 
793   /* it will need up to 5 bytes if line-breaking is enabled, however an
794    * additional byte is needed for <CR> as it is not automatically added by
795    * glib */
796   data_out = g_malloc (6);
797   len = g_base64_encode_close (TRUE, data_out, &state, &save);
798   chunk->state = state;
799   chunk->save = save;
800   /* workaround */
801   data_out[len - 1] = '\r';
802   data_out[len] = '\n';
803   /* +1 for CR */
804   g_byte_array_append (array, (guint8 *) data_out, (guint) (len + 1));
805   g_free (data_out);
806 
807   if (new_file) {
808     sink->curr_attachment++;
809     bcsink->is_live = TRUE;
810 
811     /* reset flag */
812     bcsink->new_file = FALSE;
813 
814     /* set payload headers for new file */
815     gst_curl_smtp_sink_set_payload_headers_unlocked (bcsink);
816   }
817 
818 
819   if (close_transfer && !sink->final_boundary_added)
820     add_final_boundary_unlocked (sink);
821 
822   bytes_to_send = MIN (block_size, array->len);
823   memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send);
824   g_byte_array_remove_range (array, 0, bytes_to_send);
825 
826   return bytes_to_send;
827 }
828 
829 static size_t
transfer_chunk(void * curl_ptr,TransferBuffer * buffer,Base64Chunk * chunk,size_t block_size,guint * last_chunk)830 transfer_chunk (void *curl_ptr, TransferBuffer * buffer, Base64Chunk * chunk,
831     size_t block_size, guint * last_chunk)
832 {
833   size_t bytes_to_send;
834   const guchar *data_in = buffer->ptr;
835   size_t data_in_offset = buffer->offset;
836   gint state = chunk->state;
837   gint save = chunk->save;
838   GByteArray *array = chunk->chunk_array;
839   gchar *data_out;
840 
841   bytes_to_send = MIN (block_size, buffer->len);
842 
843   if (bytes_to_send == 0) {
844     bytes_to_send = MIN (block_size, array->len);
845   }
846 
847   /* base64 encode data */
848   if (buffer->len > 0) {
849     gsize len;
850     gchar *ptr_in;
851     gchar *ptr_out;
852     gsize size_out;
853     gint i;
854 
855     /* if line-breaking is enabled, at least: ((len / 3 + 1) * 4 + 4) / 72 + 1
856      * bytes of extra space is required. However, additional <CR>'s are
857      * required, thus we need ((len / 3 + 2) * 4 + 4) / 72 + 2 extra bytes.
858      */
859     size_out = (bytes_to_send / 3 + 1) * 4 + 4 + bytes_to_send +
860         ((bytes_to_send / 3 + 2) * 4 + 4) / 72 + 2;
861 
862     data_out = g_malloc (size_out);
863     len = g_base64_encode_step (data_in + data_in_offset, bytes_to_send, TRUE,
864         data_out, &state, &save);
865     chunk->state = state;
866     chunk->save = save;
867 
868     /* LF->CRLF filter */
869     ptr_in = ptr_out = data_out;
870     for (i = 0; i < len; i++) {
871       if (*ptr_in == '\n') {
872         *ptr_in = '\r';
873         g_byte_array_append (array, (guint8 *) ptr_out, ptr_in - ptr_out);
874         g_byte_array_append (array, (guint8 *) "\r\n", strlen ("\r\n"));
875         ptr_out = ptr_in + 1;
876       }
877       ptr_in++;
878     }
879     if (ptr_in - ptr_out) {
880       g_byte_array_append (array, (guint8 *) ptr_out, ptr_in - ptr_out);
881     }
882 
883     g_free (data_out);
884     data_out = NULL;
885 
886     buffer->offset += bytes_to_send;
887     buffer->len -= bytes_to_send;
888 
889     bytes_to_send = MIN (block_size, array->len);
890     memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send);
891     g_byte_array_remove_range (array, 0, bytes_to_send);
892 
893     if (array->len == 0) {
894       *last_chunk = 1;
895 
896     }
897 
898     return bytes_to_send;
899   }
900 
901   /* at this point all data has been encoded */
902   memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send);
903   g_byte_array_remove_range (array, 0, bytes_to_send);
904   if (array->len == 0) {
905     *last_chunk = 1;
906   }
907 
908   return bytes_to_send;
909 }
910 
911 static size_t
gst_curl_smtp_sink_transfer_data_buffer(GstCurlBaseSink * bcsink,void * curl_ptr,size_t block_size,guint * last_chunk)912 gst_curl_smtp_sink_transfer_data_buffer (GstCurlBaseSink * bcsink,
913     void *curl_ptr, size_t block_size, guint * last_chunk)
914 {
915   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
916   size_t bytes_to_send;
917 
918   if (sink->payload_headers && sink->payload_headers->len) {
919     return transfer_payload_headers (sink, curl_ptr, block_size);
920   }
921 
922   if (sink->base64_chunk != NULL) {
923     bytes_to_send =
924         transfer_chunk (curl_ptr, bcsink->transfer_buf, sink->base64_chunk,
925         block_size, last_chunk);
926 
927     /* if last chunk of current buffer and max attachments per mail is reached
928      * then add final boundary */
929     if (*last_chunk && sink->curr_attachment == sink->nbr_attachments &&
930         !sink->final_boundary_added) {
931       add_final_boundary_unlocked (sink);
932       /* now that we've added the final boundary to the array we have on more
933        * chunk to send */
934       *last_chunk = 0;
935     }
936 
937     GST_OBJECT_LOCK (sink);
938     if (sink->eos) {
939       gst_curl_smtp_sink_notify_transfer_end_unlocked (sink);
940     }
941     GST_OBJECT_UNLOCK (sink);
942 
943     return bytes_to_send;
944   }
945 
946   /* we should never get here */
947   return 0;
948 }
949 
950 static size_t
transfer_payload_headers(GstCurlSmtpSink * sink,void * curl_ptr,size_t block_size)951 transfer_payload_headers (GstCurlSmtpSink * sink,
952     void *curl_ptr, size_t block_size)
953 {
954   size_t bytes_to_send;
955   GByteArray *headers = sink->payload_headers;
956 
957   bytes_to_send = MIN (block_size, headers->len);
958   memcpy ((guint8 *) curl_ptr, headers->data, bytes_to_send);
959   g_byte_array_remove_range (headers, 0, bytes_to_send);
960 
961 
962   if (headers->len == 0) {
963     g_byte_array_free (headers, TRUE);
964     sink->payload_headers = NULL;
965   }
966 
967   return bytes_to_send;
968 }
969 
970 static gboolean
gst_curl_smtp_sink_prepare_transfer(GstCurlBaseSink * bcsink)971 gst_curl_smtp_sink_prepare_transfer (GstCurlBaseSink * bcsink)
972 {
973   GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
974   CURLcode res;
975   gboolean ret = TRUE;
976 
977   if (sink->pop_location && strlen (sink->pop_location)) {
978     if ((sink->pop_curl = curl_easy_init ()) == NULL) {
979       bcsink->error = g_strdup ("POP protocol: failed to create handler");
980       return FALSE;
981     }
982 
983     res = curl_easy_setopt (sink->pop_curl, CURLOPT_URL, sink->pop_location);
984     if (res != CURLE_OK) {
985       bcsink->error = g_strdup_printf ("failed to set URL: %s",
986           curl_easy_strerror (res));
987       return FALSE;
988     }
989 
990     if (sink->pop_user != NULL && strlen (sink->pop_user) &&
991         sink->pop_passwd != NULL && strlen (sink->pop_passwd)) {
992       res = curl_easy_setopt (sink->pop_curl, CURLOPT_USERNAME, sink->pop_user);
993       if (res != CURLE_OK) {
994         bcsink->error = g_strdup_printf ("failed to set user name: %s",
995             curl_easy_strerror (res));
996         return FALSE;
997       }
998 
999       res = curl_easy_setopt (sink->pop_curl, CURLOPT_PASSWORD,
1000           sink->pop_passwd);
1001       if (res != CURLE_OK) {
1002         bcsink->error = g_strdup_printf ("failed to set user name: %s",
1003             curl_easy_strerror (res));
1004         return FALSE;
1005       }
1006     }
1007   }
1008 
1009   if (sink->pop_curl != NULL) {
1010     /* ready to initialize connection to POP server */
1011     res = curl_easy_perform (sink->pop_curl);
1012     if (res != CURLE_OK) {
1013       bcsink->error = g_strdup_printf ("POP transfer failed: %s",
1014           curl_easy_strerror (res));
1015       ret = FALSE;
1016     }
1017 
1018     curl_easy_cleanup (sink->pop_curl);
1019     sink->pop_curl = NULL;
1020   }
1021 
1022   return ret;
1023 }
1024